I would like to create a function in PL/pgSQL with a couple of nested (or inner) functions within it. This way I can break the problem down into smaller pieces but not have my smaller pieces accessible outside of this function.
Is it possible to do this in PL/pgSQL? If so, how?
Try it:
CREATE OR REPLACE FUNCTION outer() RETURNS void AS $outer$
DECLARE s text;
BEGIN
CREATE OR REPLACE FUNCTION inner() RETURNS text AS $inner$
BEGIN
RETURN 'inner';
END;
$inner$ language plpgsql;
SELECT inner() INTO s;
RAISE NOTICE '%', s;
DROP FUNCTION inner();
END;
$outer$ language plpgsql;
In postgres 9.5 SELECT outer(); outputs
psql:/vagrant/f.sql:14: NOTICE: inner
EDIT: if you don't drop the inner
function at the end of the outer function it will remain visible to the rest of the database.
Nested functions are not supported by PLpgSQL. The emulation has not any sense and it is nonproductive.
Related
I need to create a new procedure/function in Postgres that creates a new sequence.
The procedure/function will get the name of the sequence as a variable and creates it.
I tried to follow the documentation but it's not very helpful. This is what I've got so far but it's not working (of course):
CREATE FUNCTION create_seq(text) RETURNS text
AS 'CREATE SEQUENCE $1 START 100;'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
ERROR: return type mismatch in a function declared to return text
DETAIL: Function's final statement must be SELECT or INSERT/UPDATE/DELETE RETURNING.
CONTEXT: SQL function "create_seq"```
you need use dynamic SQL like said #a_horse_with_no_name, but you need use the plpgsql language not sql language for the function, for remember return void,example,:
CREATE FUNCTION create_seq_plpgsql(p_seq_name text)
RETURNS void
AS
$$
begin
execute format('CREATE SEQUENCE %I START 100', p_seq_name);
end;
$$
LANGUAGE plpgsql;
You need dynamic SQL for that, parameters can't be used as identifiers.
To properly deal with names that potentially need quoting, it is highly recommended to use the format() function to generate the SQL.
And you need to declare the function as returns void as you don't want to return anything.
CREATE FUNCTION create_seq(p_seq_name, text)
RETURNS void
AS
$$
begin
execute format('CREATE SEQUENCE %I START 100', p_seq_name);
end;
$$
LANGUAGE plpgsql;
If you are on Postgres 11, you could also use a procedure instead of a function for that.
I would like to create a function in PL/pgSQL with a couple of nested (or inner) functions within it. This way I can break the problem down into smaller pieces but not have my smaller pieces accessible outside of this function.
Is it possible to do this in PL/pgSQL? If so, how?
Try it:
CREATE OR REPLACE FUNCTION outer() RETURNS void AS $outer$
DECLARE s text;
BEGIN
CREATE OR REPLACE FUNCTION inner() RETURNS text AS $inner$
BEGIN
RETURN 'inner';
END;
$inner$ language plpgsql;
SELECT inner() INTO s;
RAISE NOTICE '%', s;
DROP FUNCTION inner();
END;
$outer$ language plpgsql;
In postgres 9.5 SELECT outer(); outputs
psql:/vagrant/f.sql:14: NOTICE: inner
EDIT: if you don't drop the inner
function at the end of the outer function it will remain visible to the rest of the database.
Nested functions are not supported by PLpgSQL. The emulation has not any sense and it is nonproductive.
I'm having problems executing a "perform create index" inside of a plgpsql function (postgres 9.4). For example:
create or replace function foo() returns void language plpgsql as $$
begin
perform 'create unique index patients_row_id_key on patients(row_id)';
end; $$;
It seems to run fine:
select foo();
However, the index is not created. Any diagnosis and workaround? I tried:
alter function foo() VOLATILE;
and still no luck.
What #Abelisto wrote about PERFORM.
And what #Chris added about SQL injection.
Plus, I suggest to use format() for anything except the most trivial query strings to make your life with dynamic SQL easier. And the manual does, too:
A cleaner approach is to use format()'s %I specification for table or column names.
CREATE OR REPLACE FUNCTION foo(_tbl text)
RETURNS void AS
$func$
BEGIN
EXECUTE format('CREATE UNIQUE INDEX %I ON %I(row_id)', _tbl || _row_id_key', _tbl);
END
$func$ LANGUAGE plpgsql;
A regclass parameter is a convenient alternative for passing table names, but concatenating new identifiers can be tricky - as this recent related case goes to show:
PL/pgSQL regclass quoting of table named like keyword
As a supplement to the point of using execute, note two important points about this.
You are doing string interpolation with sql queries (dangerous!), and
You have to use quote_ident, not quote_literal
If you use Abelisto's function above, and call it with:
SELECT foo('test_idx on test; drop table foo; --');
SQL injection in stored procedure. Worse if it is security definer. A fixed version would be:
create or replace function foo(p_tablename text) returns void language plpgsql as $$
begin
execute 'create unique index ' || quote_ident(p_tablename || '_row_id_key') || ' on ' || quote_ident(p_tablename) || '(row_id)';
end; $$;
PERFORM statement in the PLPGSQL used to execute queries which does not return result or which result is not useful. Technically PERFORM ... inside the PLPGSQL block is equal to SELECT ... in the plain SQL. So in your example you are trying to execute something like
select 'create unique index patients_row_id_key on patients(row_id)';
and just ignore the result.
Read more: Executing a Command With No Result
You should not to wrap DDL statements inside PLPGSQL and can use it as is:
create or replace function foo() returns void language plpgsql as $$
begin
create unique index patients_row_id_key on patients(row_id);
end; $$;
Or if you want to construct it at runtime then use EXECUTE statement: Executing Dynamic Commands like this:
create or replace function foo(p_tablename text) returns void language plpgsql as $$
begin
execute 'create unique index ' || p_tablename || '_row_id_key on ' || p_tablename || '(row_id)';
end; $$;
Let's say I have a function show_files(IN file text, IN suffix text, OUT statement text). In next step the function is called:
SELECT * FROM show_files(file := 'example', suffix := '.png');
My question is: Is there any solution that I could get statement that has called this function from inside that function?
I mean, after running the SELECT the output of function (OUT statement text) should be: 'SELECT * FROM show_files(file := 'example', suffix := '.png');', or is it possible to assign this statement to the variable inside the function?
I need the functionality like those with TG_NAME, TG_OP, etc. in trigger procedures.
Maybe is it possible to retrieve this statement from SELECT current_query FROM pg_stat_activity ?
When I'm trying to use it inside a function I've got an empty record:
CREATE OR REPLACE FUNCTION f_snitch(text)
RETURNS text AS
$BODY$
declare
rr text;
BEGIN
RAISE NOTICE '.. from f_snitch.';
-- do stuff
SELECT current_query into rr FROM pg_stat_activity
WHERE current_query ilike 'f_snitch';
RETURN rr;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Any help and suggestions would be happily welcome!
TG_NAME and friends are special variables that only exist for trigger functions. Regular plpgsql functions don't have anything like that. I am fresh out of ideas how you could possibly get this inside the called function in plpgsql.
You could add RAISE NOTICE to your function so you get the desired information
CREATE OR REPLACE FUNCTION f_snitch(text)
RETURNS text LANGUAGE plpgsql AS
$func$
BEGIN
RAISE NOTICE '.. from f_snitch.';
-- do stuff
RETURN 'Snitch says hi!';
END
$func$;
Call:
SELECT f_snitch('foo')
In addition to the result, this returns a notice:
NOTICE: .. from f_snitch.
Fails to please in two respects:
Calling statement is not in the notice.
No CONTEXT in the notice.
For 1. you can use RAISE LOG instead (or set your cluster up to log NOTICES, too - which I usually don't, too verbose for me). With standard settings, you get an additional line with the STATEMENT in the database log:
LOG: .. from f_snitch.
STATEMENT: SELECT f_snitch('foo')
For 2., have a look at this related question at dba.SE. CONTEXT would look like:
CONTEXT: SQL statement "SELECT f_raise('LOG', 'My message')"
PL/pgSQL function "f_snitch" line 5 at PERFORM
Ok, I've got it!
CREATE OR REPLACE FUNCTION f_snitch(text)
RETURNS setof record AS
$BODY$
BEGIN
RETURN QUERY
SELECT current_query
FROM pg_stat_activity
<strike>ORDER BY length(current_query) DESC LIMIT 1;</strike>
where current_query ilike 'select * from f_snitch%';
-- much more reliable solution
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
select * from f_snitch('koper') AS (tt text);
And here is the result:
It's probably not 100% reliable solution but for small systems (for few users) it's quite ok.
How to?
For easy example. I have a simple function:
DO LANGUAGE plpgsql $$ DECLARE
BEGIN
EXECUTE 'SELECT NOW()';
END $$;
How I can return value of "NOW()" or other values from also anonymous function? The function is given as an example I have a more complex function.
DO LANGUAGE plpgsql $$ DECLARE
BEGIN
execute '
create temporary table t
as
SELECT NOW()
';
END $$;
select * from t;
It is not an anonymous function, but rather anonymous code block.
if you need to return values, consider creating real functions;
if you need to output some debug info, just RAISE NOTICE.