Postgres function using DDL statement - postgresql-9.4

I am trying to create function in Postgres which can automate user creation process but it doesn;t accept parameter in DDL statement.
CREATE OR REPLACE FUNCTION AUTOUSER (uname varchar(20))
RETURNS TEXT AS $$
DECLARE
nm varchar(20);
BEGIN
nm=$1;
CREATE USER nm WITH PASSWORD 'Iash12';
GRANT ALL ON DATABASE iashdb TO nm;
GRANT ALL ON ALL TABLES IN SCHEMA public TO nm;
RETURN CONCAT(nm,' Created');
END;
$$
LANGUAGE plpgsql;
Above function create user as 'nm' instead of passed parameter name however RETURN statement showing correct result. Thanks in advance,

You need to use dynamic SQL and you need to quote the parameters properly. The easiest way is to use the format() function with the appropriate placeholders:
CREATE OR REPLACE FUNCTION AUTOUSER (uname varchar(20))
RETURNS TEXT AS $$
BEGIN
execute format('CREATE USER %I WITH PASSWORD %L', uname, 'Iash12');
execute format('GRANT ALL ON DATABASE iashdb TO %I', uname);
execute format('GRANT ALL ON ALL TABLES IN SCHEMA public TO %I', uname);
RETURN CONCAT(uname,' Created');
END;
$$
LANGUAGE plpgsql;
The placeholder %I properly quotes SQL identifiers. The placeholder %L properly deals with string literals.

Related

Not able to create backup of table dynamically, through PL/pgSQL function

I am trying to create a function to create table backup dynamically.
But I am getting error like :
ERROR: syntax error at or near "'
Here's one of my approach, which I am trying:
CREATE OR REPLACE FUNCTION public.test () RETURNS varchar AS
$BODY$ DECLARE backup_string varchar(50);
BEGIN
backup_string = (SELECT '_'||LPAD(DATE_PART('DAY',CURRENT_DATE)::VARCHAR,2,'0')||DATE_PART('MONTH',CURRENT_DATE)::VARCHAR||DATE_PART('YEAR',CURRENT_DATE)::VARCHAR||'_1');
EXECUTE 'SELECT * INTO table_name'|| backup_string ||' FROM table_name';
RETURN 'Y';
EXCEPTION WHEN others THEN RETURN 'N';
END
; $BODY$
LANGUAGE 'plpgsql'
GO
SELECT * FROM test()
I am not getting, why that execute statement giving me error like that.
I suggest so simplify your code and make use of the format() function to generate the dynamic SQL. That way you can avoid the clutter that concatenation generates and you can concentrate on the actual SQL code. In addition to that it also properly deals with identifiers that might need quoting.
When dealing with dynamic SQL it's always a good idea to store the generated SQL statement in a variable, so that it can be printed for debugging purposes if you get an error. Looking at the generated SQL usually tells you where the generation code went wrong.
CREATE OR REPLACE FUNCTION test()
RETURNS varchar
AS
$BODY$
DECLARE
l_source_table text;
l_backup_table text;
l_sql text;
BEGIN
l_source_table := 'table_name';
l_backup_table := l_source_table||'_'||to_char(current_date, 'ddmmyyyy')||'_1';
l_sql := format('create table %I as select * from %I', l_backup_table, l_source_table);
-- for debugging purposes:
raise notice 'Running: %', l_sql
EXECUTE l_sql;
RETURN 'Y';
EXCEPTION
WHEN others THEN RETURN 'N';
END;
$BODY$
LANGUAGE plpgsql;
Note that I also used variables for the source and backup table to be able to use that as a place holder for the format() function.
Online example

how can I create a stored procedure sql workbench that uses a redshift database?

is it possible to create a stored procedures on sql workbench that uses a redshift database ?
I tried to put in some procedure found on the internet like this one
CREATE OR REPLACE FUNCTION proc_sample RETURN INTEGER
IS
l_result INTEGER;
BEGIN
SELECT max(col1) INTO l_result FROM sometable;
RETURN l_result;
END;
but I get an error
the cursor is not located inside a statement
help please.
Here is my translation of your stored procedure for Redshift:
CREATE OR REPLACE PROCEDURE proc_sample (
l_result OUT INTEGER
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT max(col1) INTO l_result FROM sometable;
END
$$;
You call this stored procedure in Redshift as follows:
BEGIN; CALL proc_sample(); END;
-- l_result
-- ----------
-- 99
For more information see "Overview of stored procedures in Amazon Redshift"
you can not use from clause in function. you have to use procedure having parameter with out clause.

Create a procedure/function that creates new sequence

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.

"perform create index" in plpgsql doesn't run

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; $$;

PostgreSQL 9.3 trigger function to insert into table with parameterized name

I'm trying to dynamically partition log entries in Postgres. I have 53 child tables (1 for each week's worth of log entries), and would like to route INSERTs to a child table using a trigger.
I run the function with INSERT INTO log5 VALUES (NEW.*), and it works.
I run the function with the EXECUTE statement instead, and it fails. Within the EXECUTE statement, it's recognizing NEW as a table name and not a variable passed to the trigger function. Any ideas on how to fix? Thanks!
The error:
QUERY: INSERT INTO log5 VALUES (NEW.*)
CONTEXT: PL/pgSQL function log_roll_test() line 6 at EXECUTE statement
ERROR: missing FROM-clause entry for table "new" SQL state: 42P01
My function:
CREATE FUNCTION log_roll_test() RETURNS trigger AS $body$
DECLARE t text;
BEGIN
t := 'log' || extract(week FROM NEW.updt_ts); --child table name
--INSERT INTO log5 VALUES (NEW.*);
EXECUTE format('INSERT INTO %I VALUES (NEW.*);', t);
RETURN NULL;
END;
$body$ LANGUAGE plpgsql;
My trigger:
CREATE TRIGGER log_roll_test
BEFORE INSERT ON log FOR EACH ROW
EXECUTE PROCEDURE log_roll_test();
CREATE FUNCTION log_roll_test()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('INSERT INTO %I SELECT ($1).*' -- !
, to_char(NEW.updt_ts, '"log"WW')) -- child table name
USING NEW; -- !
RETURN NULL;
END
$func$;
You cannot reference NEW inside the query string. NEW is visible in the function body, but not inside EXECUTE environment. The best solution is to pass values in the USING clause.
I also substituted the equivalent to_char(NEW.updt_ts, '"log"WW') for the table name. to_char() is faster and simpler here.