I'm trying to call a polymorphic function where the table type can be determined by a subsidiary function, say, which_table(). The problem is that I can't convert the returned varchar into an actual type
I'm trying to base a solution off of Erwin Brandstetter's "Various complete table types" section of one of his previous SO answers, as well as his comments in the answer to SQL Injection-safe call of polymorphic function. So, referencing examples there, the behavior I want is to be able to do
SELECT * FROM data_of(NULL::pcdmet, 17);
but be able to specify the table name dynamically, such as,
SELECT * FROM data_of( which_table('arg that evaluates totypeNULL::pcdmet') , 17 )
In my case, the pcdmet, "table" types can be designed to be either all regular tables, temp tables, or composite types (so a solution using any of these would be fully acceptable).
Issue #1
I've been trying to use a PREPARE/EXECUTE approach as suggested, but haven't been able to see what I'm doing wrong.
create or replace function fooprep (inint int, inchar varchar) RETURNS varchar
AS $$
begin
RETURN ''||inint||' '||inchar;
end;
$$ language plpgsql;
dev_db=> create type int_char as (coli int, colv varchar);
CREATE TYPE
dev_db=> prepare fooplan (int_char) as
select * from fooprep($1, $2);
ERROR: function fooprep(int_char, unknown) does not exist
LINE 2: select * from fooprep($1, $2);
Issue #2
But further more, even if I get #1 to work, how to I return it as a type from which_table() when I don't know which table that function will return? I've tried specifying regclass as the return type for a stub which_table() that just returns a constant NULL::testtable but that doesn't seem to be usable as a data type (I'd expected to be able to use it as a table type)
I've also tried something along the lines of
create or replace FUNCTION foofn (bar anyelement DEFAULT EXECUTE fooplan_mod(5))
but get an error there, too:
ERROR: syntax error at or near "fooplan"
LINE 1: ...ce FUNCTION foofn (bar anyelement DEFAULT EXECUTE fooplan_mod(5)...
^
I've tried a plethora of other things to the point that I've pretty much abandoned keyword capitalization (as you can see :-). I feel like I must be close but overlooking something.
I'm using PostgreSQL 13: psql (13.9 (Ubuntu 13.9-1.pgdg20.04+1), server 13.10 (Ubuntu 13.10-1.pgdg20.04+1))
Try the following approach using polymorphic types:
CREATE FUNCTION selectit(anycompatible, bigint)
RETURNS SETOF anycompatible
LANGUAGE plpgsql
AS $$BEGIN
RETURN QUERY EXECUTE
format('SELECT * FROM %s LIMIT %s',
pg_typeof($1),
$2);
END;$$;
Here is a test:
CREATE TABLE test (id integer PRIMARY KEY, value text);
INSERT INTO test VALUES (1, 'one'), (2, 'two'), (3, 'three');
SELECT * FROM selectit(NULL::test, 2);
id │ value
════╪═══════
1 │ one
2 │ two
(2 rows)
Related
CREATE TABLE Person (
id serial primary key,
accNum text UNIQUE GENERATED ALWAYS AS (
concat(right(cast(extract year from current_date) as text), 2), cast(id as text)) STORED
);
Error: generation expression is not immutable
The goal is to populate the accNum field with YYid where YY is the last two letters of the year when the person was added.
I also tried the '||' operator but it was unsuccessful.
As you don't expect the column to be updated, when the row is changed, you can define your own function that generates the number:
create function generate_acc_num(id int)
returns text
as
$$
select to_char(current_date, 'YY')||id::text;
$$
language sql
immutable; --<< this is lying to Postgres!
Note that you should never use this function for any other purpose. Especially not as an index expression.
Then you can use that in a generated column:
CREATE TABLE Person
(
id integer generated always as identity primary key,
acc_num text UNIQUE GENERATED ALWAYS AS (generate_acc_num(id)) STORED
);
As #ScottNeville correctly mentioned:
CURRENT_DATE is not immutable. So it cannot be used int a GENERATED ALWAYS AS expression.
However, you can achieve this using a trigger nevertheless:
demo:db<>fiddle
CREATE FUNCTION accnum_trigger_function()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
NEW.accNum := right(extract(year from current_date)::text, 2) || NEW.id::text;
RETURN NEW;
END
$$;
CREATE TRIGGER tr_accnum
BEFORE INSERT
ON person
FOR EACH ROW
EXECUTE PROCEDURE accnum_trigger_function();
As #a_horse_with_no_name mentioned correctly in the comments: You can simplify the expression to:
NEW.accNum := to_char(current_date, 'YY') || NEW.id;
I am not exactly sure how to solve this problem (maybe a trigger), but current_date is a stable function not an immutable one. For the generated IDs I believe all function calls must be immutable. You can read more here https://www.postgresql.org/docs/current/xfunc-volatility.html
I dont think any function that gets the date can be immutable as Postgres defines this as "An IMMUTABLE function cannot modify the database and is guaranteed to return the same results given the same arguments forever." This will not be true for anything that returns the current date.
I think your best bet would be to do this with a trigger so on insert it sets the value.
Goal
I'm trying to make PostgreSQL do something like function dispatch, but I'm open to any other solution for the problem that lets me keep the function I'm calling a SQL function (versus PL/PGSQL) because I want it to be inlined.
Suppose I have two functions like:
create or replace function is_in_view_one(p people) returns boolean as $$
select p.state = ANY(ARRAY['TX', 'NY', 'CA'])
$$ language sql strict stable;
And:
create or replace function is_in_view_two(p people) returns boolean as $$
select p.state = ANY(ARRAY['NV', 'FL', 'MT'])
$$ language sql strict stable;
I want to be able to write some code, or adapt the above functions, so I can write:
select count(*)
from people
where is_in_view(people, 'one');
And I want is_in_view to be fully inline-able according to these criteria: https://wiki.postgresql.org/wiki/Inlining_of_SQL_functions'
Attempted Solution via Domains
I've tried to set up a solution using domains as function identifiers, and although it doesn't work, I think someone more knowledgeable about PostgreSQL types, casts, and function identification might know how to hack it.
I tried to do:
create domain view_one_id as uuid check (value = 'ed744964-6561-11eb-878e-c7ad77d3260a');
create domain view_two_id as uuid check (value = 'fa9fe0f8-6561-11eb-878e-c79c81b46d0c');
create or replace function say_n(v view_one_id) returns integer as $$
select 1
$$ language sql strict;
create or replace function say_n(v view_two_id) returns integer as $$
select 2
$$ language sql strict;
Hoping that I could then do:
select say_n('ed744964-6561-11eb-878e-c7ad77d3260a') # 1
select say_n('fa9fe0f8-6561-11eb-878e-c79c81b46d0c') # 2
But instead I get:
=# select say_n('fa9fe0f8-6561-11eb-878e-c79c81b46d0c');
ERROR: function say_n(unknown) is not unique
LINE 1: select say_n('fa9fe0f8-6561-11eb-878e-c79c81b46d0c');
^
HINT: Could not choose a best candidate function. You might need to add explicit type casts.
I can do:
=# select say_n('fa9fe0f8-6561-11eb-878e-c79c81b46d0c'::view_two_id);
say_n
-------
2
(1 row)
But in order to integrate with external tooling that knows how to call functions and only call functions (not also supply a variable type cast) I'm holding out hope for a solution that doesn't require modifications to this external tooling.
Happy to entertain alternatives! I feel like this solution might be possible by fiddling with the CAST or something, however.
What about using CASE:
select p.state = ANY (CASE WHEN $2 = 'one'
THEN ARRAY['TX', 'NY', 'CA']
ELSE ARRAY['NV', 'FL', 'MT']
END)
But if you want efficiency, you would be better off with the two functions, because the above cannot use an index.
I'm trying to build a parametrized view using a postgres function:
CREATE FUNCTION schemaB.testFunc(p INT)
RETURNS TABLE
AS
RETURN (SELECT * FROM schemaZ.mainTable WHERE id=p)
The problem is always the same:
SQL Error [42601]: ERROR: syntax error at or near "AS"
Any idea on what could I be doing wrong?
You need to specify the columns of the "return table", this is either done using
returns table(col_1 integer, col_2 text, ...)
In your case you are returning only rows of one table, so it's easier to use
returns setof maintable
As documented in the manual the function body needs to be enclosed in single quotes, or using dollar quoting.
As stored functions can be written in many different languages in Postgres, you also need to specify a language - in this case language sql is suitable.
So putting all that together, you need:
CREATE FUNCTION schemaB.testFunc(p_id INT)
RETURNS setof schemaZ.mainTable
AS
$$
SELECT *
FROM schemaZ.mainTable
WHERE id = p_id
$$
language sql;
A return statement is not required for language sql functions.
I am trying to implement a function that returns a table with the same structure as an input table in the parameter, using PL/pgSQL (PostgreSQL 9.3). Basically, I want to update a table, and return a copy of the updated table with plpgsql. I searched around SO and found several related questions (e.g. Return dynamic table with unknown columns from PL/pgSQL function and Table name as a PostgreSQL function parameter), which lead to the following minimal test example:
CREATE OR REPLACE FUNCTION change_val(_lookup_tbl regclass)
RETURNS _lookup_tbl%rowtype AS --problem line
$func$
BEGIN
RETURN QUERY EXECUTE format('UPDATE %s SET val = 2 RETURNING * ; ', _lookup_tbl);
END
$func$ LANGUAGE plpgsql;
But I can't get past giving the correct return type for TABLE or SETOF RECORD in the problem line. According to this answer:
SQL demands to know the return type at call time
But I think the return type (which I intend to borrow from the input table type) is known. Can some one help explain if it is possible to fix the signature of the above PL/pgSQL function?
Note, I need to parametrize the input table and return the update of that table. Alternatives are welcome.
What you have so far looks good. The missing ingredient: polymorphic types.
CREATE OR REPLACE FUNCTION change_val(_tbl_type anyelement)
RETURNS SETOF anyelement -- problem solved
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format(
'UPDATE %s SET val = 2 RETURNING *;'
, pg_typeof(_tbl_type))
);
END
$func$;
Call (important):
SELECT * FROM change_val(NULL::some_tbl);
db<>fiddle here
Old sqlfiddle
See (last paragraph):
Refactor a PL/pgSQL function to return the output of various SELECT queries
I am developing a framework that dynamically creates tables for contents storage on PostgreSQL 9.1. One of the API functions allows caller to save a new contents entry by specifying all fields within a given object (say, web form). In order to receive a set of fields framework creates a composite type.
Consider the following code:
CREATE SEQUENCE seq_contents MINVALUE 10000;
CREATE TABLE contents (
content_id int8 not null,
is_edited boolean not null default false,
is_published boolean not null default false,
"Input1" varchar(60),
"CheckBox1" int2,
"TheBox" varchar(60),
"Slider1" varchar(60)
);
CREATE TYPE "contentsType" AS (
"Input1" varchar(60),
"CheckBox1" int2,
"TheBox" varchar(60),
"Slider1" varchar(60)
);
CREATE OR REPLACE FUNCTION push(in_all anyelement) RETURNS int8 AS $push$
DECLARE
_c_id int8;
BEGIN
SELECT nextval('seq_contents') INTO _c_id;
EXECUTE $$INSERT INTO contents
SELECT a.*, b.*
FROM (SELECT $1, true, false) AS a,
(SELECT $2.*) AS b$$ USING _c_id, in_all;
RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;
Now, in order to call this function I have to add explicit cast, like this:
SELECT push(('input1',1,'thebox','slider1')::"contentsType");
Is there a way to avoid explicit cast? As I would like external callers not to deal with casts, i.e. hide the logic behind the PostgreSQL functions. Currently I have such error:
SELECT push(('input1',1,'thebox','slider1'));
ERROR: PL/pgSQL functions cannot accept type record
CONTEXT: compilation of PL/pgSQL function "push" near line 1
Have you considered passing the record variable as its text representation?
In theory, every record variable can be cast to and from text with the normal CAST operator.
Here is the function modified so that in_all has type text and gets casted to "contentsType" in the USING clause:
CREATE OR REPLACE FUNCTION push(in_all text) RETURNS int8 AS $push$
DECLARE
_c_id int8;
BEGIN
SELECT nextval('seq_contents') INTO _c_id;
EXECUTE $$INSERT INTO contents
SELECT a.*, b.*
FROM (SELECT $1, true, false) AS a,
(SELECT $2.*) AS b$$ USING _c_id, in_all::"contentsType";
RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;
Then it can be called like this (no explicit reference to the type)
select push( '(input1,1,thebox,slider1)' );
or like that (explicit record casted to text)
SELECT push(('input1',1,'thebox','slider1')::"contentsType"::text);
That would work not just with "contentsType", but any other record type, assuming the function is able to convert it back to that type.
Also in plpgsql, I assume this should work as well:
ret := push(r::text);
when r is a record variable.
Since you're hard-coding the table name into which you want to insert, and you have a fixed number and type of parameters it needs, I'm not clear on why you need the "contentsType" type at all. Why not eliminate the extra level of parentheses from the function calling, and just pass the four parameters directly? That keeps everything simpler.
CREATE OR REPLACE FUNCTION push(
"Input1" varchar(60),
"CheckBox1" int2,
"TheBox" varchar(60),
"Slider1" varchar(60)
) RETURNS int8 AS $push$
DECLARE
_c_id int8;
BEGIN
SELECT nextval('seq_contents') INTO _c_id;
EXECUTE $$INSERT INTO contents
VALUES ($1, true, false, $2, %3, %4, $5)
$$ USING _c_id, "Input1", "CheckBox1", "TheBox", "Slider1");
RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;
That makes calling the function look like this:
SELECT push('input1',1,'thebox','slider1');
If you're looking to generalized the push() function so that it works for all tables, you'll hit other problems if you get past this one. You won't be able to get past the fact that the function will need to know the table name during execution. If you want to overload the function so that you can have a separate push() for each record type, you need to provide information on the record type somehow. So, if you're looking to do something like this, the short answer to your question is "No."
On the other hand, you may be making this a little harder than it needs to be. I hope you are aware that there is automatically a type created for every table, by the same name as the table. You could probably leverage that to both avoid declaring the type explicitly and to pass a record with the same name as your table -- with dummy entries for the values that the function will fill. I think you could make one totally generic push function, although it might be hard to get past the strong typing issues in plpgsql; writing the function in C might be easier if you're familiar with it.