Create array of custom domain postgres - sql

Because of the inherit limitations of enum (you can't add values to the enum from within a function), I'm switching to custom domains with a check constraint verifying the values. I need to be able to create arrays of my custom enums, but when I try something like this:
CREATE DOMAIN foo AS text CHECK (VALUE IN ('foo', 'bar'));
CREATE TABLE foo_table(foo_column foo[]);
I get the error
type "foo[]" does not exist
Doing some googling, I found this from 2004 which made it look like support for this was coming. Is there a way to do this?
Thanks!
UPDATE
I've come up with a hacky solution, which I'll put as the answer if no one comes up with a better solution in a few days. This solution means you can't reuse a type to be an array, you have to create a separate type that acts as the array:
CREATE DOMAIN foo_group AS text[] CHECK (VALUE <# ARRAY['foo', 'bar']);
CREATE TABLE foo_table(foo_column foo_group);
The following work:
INSERT INTO foo_table VALUES(ARRAY['foo']);
INSERT INTO foo_table VALUES(ARRAY['foo', 'bar']);
INSERT INTO foo_table VALUES(ARRAY['bar']);
The following don't:
INSERT INTO foo_table VALUES(ARRAY['foo', 'baz']);
INSERT INTO foo_table VALUES(ARRAY['baz']);

Another possible workaround is:
CREATE TYPE foo_tup AS (item foo);
Domain types can wrapped in tuples like this and that gives you an array constructor. The downside is now you probably want to create casts:
select array[row('foo')::foo_tup, row('bar')];
For example you could create a function and a cast:
create function foo_tup(foo) returns foo_tup language sql as $$
select row($1)::foo_tup;
$$ immutable;
create function foo(foo_tup) returns foo language sql as $$
select $1.item;
$$;
create cast (foo as foo_tup) with function foo_tup(foo);
create cast (foo_tup as foo) with function foo(foo_tup);
Then aggregation becomes easy:
select array_agg(myfoo::foo_tup) from my_table;
though you get extra parentheses.

Related

Define ordering scheme for user defined domain type

Given a domain like
CREATE DOMAIN my_type AS TEXT NOT NULL;
And a table
CREATE TABLE my_types (
id SERIAL PRIMARY KEY,
my_value my_type
);
INSERT INTO my_types (my_value) VALUES ('not an asterisk'), ('*'), ('also not an asterisk');
I can define a sorting function like this:
CREATE OR REPLACE FUNCTION
sort_my_type(my_type)
RETURNS INT LANGUAGE plpgsql IMMUTABLE AS
$$
BEGIN
IF $1 = '*' THEN
RETURN -1;
ELSE
RETURN 1;
END IF;
END;
$$;
And use it to order my query:
SELECT * FROM my_types ORDER BY sort_my_type(my_value);
Which makes sure that any value that equals '*' will be first.
How can I drop the need to specify the function? I'm not really interested in implementing comparison functions (unless I need to), I just want to define a "natural order" for my_type. It seems to essentially inherit its default ordering scheme from TEXT as of now.
So the desired outcome is that the query looks like
SELECT * FROM my_types ORDER BY my_value;
(This happens to work currently, because my desired outcome in this example happens to coincide with how TEXT is ordered. The real example is a little more thorough and cannot rely on the default ordering of text.)
Quoting Gordon Linoff's:
You can't do this with DOMAIN because that is essentially a subset of an existing type -- unless there is a collation that matches what you want. You could define a customer collation, but that might be more trouble than it is worth

Algebraic Data Types in Postgres

Is it possible to create an Algebraic Data Type in Postgres and then use it as a column type?
For example:
CREATE TYPE hoofed AS ENUM('horse', 'goat');
CREATE TYPE monkey AS ENUM('chimp','macaque');
CREATE TYPE ANIMAL AS ENUM(hoofed, monkey);
This fails with:
syntax error at or near "hoofed"
LINE 1: CREATE TYPE ANIMAL AS ENUM(hoofed, monkey);
Is it possible to do something like this?
Ultimately what I would then like to be able to do is something like so:
CREATE TABLE zoo (
a ANIMAL,
name text
);
INSERT INTO zoo(a, name) VALUES('horse', 'bob');
INSERT INTO zoo(a, name) VALUES('macaque', 'jimmy');
And for both of the records to be independently valid.
EDIT: #Abihabi87's response below does allow me to create, in effect, a product type, but it still does not allow me to create a union type as desired.
You cant create type enum from others enum type:
you can create ANIMAL that like:
CREATE TYPE ANIMAL AS (h hoofed,m monkey);
Example in use:
CREATE TABLE your_table
(
a ANIMAL
);
INSERT INTO your_table(a) select (select ('horse','macaque')::ANIMAL);
With ENUM types, you cannot achieve dynamic type composition/union. However, with DOMAIN types, you could achieve something similar:
create function valid_any_domain(anyelement, variadic regtype[])
returns boolean
language plpgsql
immutable
as $func$
declare
t regtype;
begin
foreach t in array $2 loop
begin
execute format('select $1::%s', t) using $1;
exception
when not_null_violation or check_violation then
continue;
end;
return true;
end loop;
return false;
end;
$func$;
create domain hoofed as text
check (value in ('horse', 'goat'));
create domain monkey as text
check (value in ('chimp','macaque'));
create domain animal as text
check (valid_any_domain(value, 'hoofed', 'monkey'));
Changing the base types will dynamically change the composite/union type too, but still requires a manual constraint validation (especially, when some value(s) are removed from the valid spectrum):
alter domain hoofed drop constraint hoofed_check;
alter domain hoofed add check (value in ('horse', 'goat', 'zebra'));
alter domain animal validate constraint animal_check;
http://rextester.com/MBVC62095
Note: however, with DOMAIN types, you will lose an ENUM property: the custom ordering. DOMAINs will always use the underlying type's ordering.
Use the function:
create or replace function create_enum(name, variadic regtype[])
returns void language plpgsql as $$
begin
execute format(
'create type %I as enum(%s)',
$1,
string_agg(quote_literal(enumlabel), ',' order by enumtypid, enumsortorder))
from pg_enum
where enumtypid = any($2);
end $$;
Pass the name of a new type and a list of enum types as arguments:
select create_enum('animal', 'hoofed', 'monkey');
select enum_range(null::animal) as animal;
animal
----------------------------
{horse,goat,chimp,macaque}
(1 row)
Effectively you are trying to merge two enum types.
There are some open questions:
Can there be duplicate strings?
Is the design supposed to be static (changes to enum type hoofed do not change type animal later) or dynamic (the opposite).
Merge exactly two enum types or more?
Since the order of elements is significant, what is the order of elements in animal supposed to be?
Is this a one-time operation or intended for repeated use?
Assuming no duplicates, static design, two enum types, existing order of elements as appended and one-time operation.
You can use the built-in enum support function enum_range(anyenum) to get an array of all elements for a given enum type.
DO
$$
BEGIN
EXECUTE (
SELECT 'CREATE TYPE animal AS ENUM ('
|| array_to_string(enum_range(null::hoofed)::text[]
|| enum_range(null::monkey)::text[], ''',''')
|| ''')'
);
END
$$;

Input table for PL/pgSQL function

I would like to use a plpgsql function with a table and several columns as input parameter. The idea is to split the table in chunks and do something with each part.
I tried the following function:
CREATE OR REPLACE FUNCTION my_func(Integer)
RETURNS SETOF my_part
AS $$
DECLARE
out my_part;
BEGIN
FOR i IN 0..$1 LOOP
FOR out IN
SELECT * FROM my_func2(SELECT * FROM table1 WHERE id = i)
LOOP
RETURN NEXT out;
END LOOP;
END LOOP;
RETURN;
END;
$$
LANGUAGE plpgsql;
my_func2() is the function that does some work on each smaller part.
CREATE or REPLACE FUNCTION my_func2(table1)
RETURNS SETOF my_part2 AS
$$
BEGIN
RETURN QUERY
SELECT * FROM table1;
END
$$
LANGUAGE plpgsql;
If I run:
SELECT * FROM my_func(99);
I guess I should receive the first 99 IDs processed for each id.
But it says there is an error for the following line:
SELECT * FROM my_func2(select * from table1 where id = i)
The error is:
The subquery is only allowed to return one column
Why does this happen? Is there an easy way to fix this?
There are multiple misconceptions here. Study the basics before you try advanced magic.
Postgres does not have "table variables". You can only pass 1 column or row at a time to a function. Use a temporary table or a refcursor (like commented by #Daniel) to pass a whole table. The syntax is invalid in multiple places, so it's unclear whether that's what you are actually trying.
Even if it is: it would probably be better to process one row at a time or rethink your approach and use a set-based operation (plain SQL) instead of passing cursors.
The data types my_part and my_part2 are undefined in your question. May be a shortcoming of the question or a problem in the test case.
You seem to expect that the table name table1 in the function body of my_func2() refers to the function parameter of the same (type!) name, but this is fundamentally wrong in at least two ways:
You can only pass values. A table name is an identifier, not a value. You would need to build a query string dynamically and execute it with EXECUTE in a plpgsql function. Try a search, many related answers her on SO. Then again, that may also not be what you wanted.
table1 in CREATE or REPLACE FUNCTION my_func2(table1) is a type name, not a parameter name. It means your function expects a value of the type table1. Obviously, you have a table of the same name, so it's supposed to be the associated row type.
The RETURN type of my_func2() must match what you actually return. Since you are returning SELECT * FROM table1, make that RETURNS SETOF table1.
It can just be a simple SQL function.
All of that put together:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Note the parentheses, which are essential for decomposing a row type. Per documentation:
The parentheses are required here to show that compositecol is a column name not a table name
But there is more ...
Don't use out as variable name, it's a keyword of the CREATE FUNCTION statement.
The syntax of your main query my_func() is more like psudo-code. Too much doesn't add up.
Proof of concept
Demo table:
CREATE TABLE table1(table1_id serial PRIMARY KEY, txt text);
INSERT INTO table1(txt) VALUES ('a'),('b'),('c'),('d'),('e'),('f'),('g');
Helper function:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Main function:
CREATE OR REPLACE FUNCTION my_func(int)
RETURNS SETOF table1 AS
$func$
DECLARE
rec table1;
BEGIN
FOR i IN 0..$1 LOOP
FOR rec IN
SELECT * FROM table1 WHERE table1_id = i
LOOP
RETURN QUERY
SELECT * FROM my_func2(rec);
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM my_func(99);
SQL Fiddle.
But it's really just a a proof of concept. Nothing useful, yet.
As the error log is telling you.. you can return only one column in a subquery, so you have to change it to
SELECT my_func2(SELECT Specific_column_you_need FROM hasval WHERE wid = i)
a possible solution can be that you pass to funct2 the primary key of the table your funct2 needs and then you can obtain the whole table by making the SELECT * inside the function

SQL Function (plpgsql) Type-Defined Outputs?

I've been defining the output of methods as...
CREATE TABLE person AS (
id serial PRIMARY KEY,
name text NOT NULL,
fingerprint text
);
CREATE TYPE person_output AS (
name text
);
CREATE OR REPLACE FUNCTION my_schema.create_person(_name text)
RETURNS person_output AS $$
DECLARE
output person_output;
BEGIN
INSERT INTO person (name) VALUES (_name) RETURNING name INTO person_output;
RETURN person_output;
END;
$$ LANGUAGE plpgsql;
I'm wondering if this rule of creating a type for what the function should output is standard within industry? Or, would it be better to create a view and just return 1 or raised errors depending if it succeeded or failed, and then calling the view in server-side python code to return?
I could see type-based outputs being good for creating "upgrades" like, CREATE TYPE person_output_v002 for the next-version, and creating a template for adapting, but then again, that wouldn't be needed if there were views.
The only reason I have decided not to use views is because I have things like groups of people and I'd like to create views for the groups, but I'd need to add a WHERE group_id = _GROUP_ID type of thing when selecting the view which I think isn't possible.
Might there be a way to select VIEWS with additional query parameters?
---- UPDATE ------
When there are certain outputs like current_user repeated as RETURN _current_user_output across multiple FUNCTIONs, is it a good idea to create one TYPE current_user_output so that we can be sure all FUNCTIONs which output the current_user are returning the same thing?
You do not need a row type for that at all. Not even a PL/pgSQL function. Simplify:
CREATE OR REPLACE FUNCTION my_schema.create_person(_name text)
RETURNS text AS
$func$
INSERT INTO person (name)
VALUES (_name)
RETURNING name;
$func$ LANGUAGE sql;
There is no rule of creating a type for what the function should output. The requirement is to declare the return type, and that's required by SQL which demands to know the type at execution time. To return multiple columns, you don't have to create a type. You can use RETURNS TABLE:
RETURNS TABLE (col1 int, col2 text, ...)
I suggest to read these two chapters of the manual:
Returning From a Function
CREATE FUNCTION
And you can use the readily defined row type of existing tables or polymorphic types:
Refactor a PL/pgSQL function to return the output of various SELECT queries
Added question:
... so that we can be sure all FUNCTIONs which output the current_user are returning the same thing?
If your functions just return text, you can be just as sure. It might make sense for row-types. Then it can be useful to define a composite type to share. Complicates maintenance. If you want to change the return type, you have to change alls functions at once. But that may be as intended ...

Is it possible to have PostgreSQL function return “arbitrary” type?

Is it possible to have an arbitrary return type defined for some PostgreSQL function?
The idea is that depending on the call, the output may be returned differently.
For example, let’s say we have:
TypeA : (name, email)
TypeB : (name, email, address, phone)
We may have a function:
func1(name varchar);
But return type could be either: TypeA or TypeB
So, is it possible to define func1, so that the arbitrary return type works?
EDIT:
IF the solution is refcursor ... Could someone please write an answer based on the example in my question? That would help a lot!
You have a few options. The first is to use a polymorphic type which would be cast on call, the second would be to use a cast, and a third would be to return a refcursor.
Polymorphic type
In this case, you'd do something like:
CREATE FUNCTION foo (bar varchar, baz ANYELEMENT) returns ANYELEMENT AS
$$
SELECT 'test'::TEXT;
$$ language sql;
Then to call it you would cast the NULL argument on call:
SELECT * FROM foo('test', null::varchar);
The real problem you have with this is that you are going to have to specify a type on every call.
Single Return type with Cast
In your example, one type has a subset of fields of another type. So you could:
CREATE TYPE all_info AS (
name text,
email text,
address text,
phone text
);
CREATE TYPE email_only AS (
name text,
email text
);
CREATE FUNCTION email_only(all_info) returns email_only LANGUAGE SQL IMMUTABLE AS $$
SELECT $1.name, $1.email;
$$;
CREATE CAST (all_info as email_only) WITH FUNCTION email_only(all_info);
Then you create your function to return all_info and you can cast on output. Something like:
SELECT (f::email_only).* FROM my_function('foo') f;
Note these two allow you to use SQL language functions which refcursors do not.
Refcursor
In this case you have to use plpgsql
CREATE OR REPLACE FUNCTION foo(bar varchar) RETURNS refcursor LANGUAGE plpgsql AS
$$
DECLARE a REFCURSOR;
BEGIN
OPEN a FOR SELECT ....;
RETURN a;
END;
$$;
In general I think it is easiest to start with the superset and cast approach than it is the others. Refcursors are possibly a second approach. Last would be insisting on a cast for a type.