Creating Trigger function in pgAdmin 4 - postgresql

I'm trying to create a trigger function in the PostgreSQL database with pgAdmin 4. The function should perform pg_notify and return newly inserted data in JSON. But I'm getting the error and can't figure out where is a mistake.
Code:
CREATE FUNCTION ba_weather.weather_notify_func()
RETURNS trigger
LANGUAGE 'plpgsql'
NOT LEAKPROOF
AS $BODY$ CREATE OR REPLACE FUNCTION weather_notify_func()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
PERFORM pg_notify('weather_insert', row_to_json(NEW));
RETURN NEW;
END;
$$;$BODY$;
ALTER FUNCTION ba_weather.weather_notify_func()
OWNER TO me;
Error:
enter code here
ERROR: syntax error at or near "CREATE"
LINE 5: AS $BODY$ CREATE OR REPLACE FUNCTION weather_notify_func()
^
The solution is:
CREATE FUNCTION ba_weather.weather_notify_func()
RETURNS trigger
LANGUAGE 'plpgsql'
NOT LEAKPROOF
AS $BODY$
BEGIN
PERFORM pg_notify('weather_insert', row_to_json(NEW));
RETURN NEW;
END;
$BODY$;
ALTER FUNCTION ba_weather.weather_notify_func()
OWNER TO me;

You nested the function definition again into the create function statement:
CREATE FUNCTION ba_weather.weather_notify_func()
RETURNS trigger
LANGUAGE plpgsql
NOT LEAKPROOF
AS $BODY$
BEGIN
PERFORM pg_notify('weather_insert', row_to_json(NEW));
RETURN NEW;
END;
$BODY$;
ALTER FUNCTION ba_weather.weather_notify_func()
OWNER TO me;

Related

Create fuction in pgplsql has error in if condition

I am trying to create function in postgres
CREATE OR REPLACE FUNCTION cms_inventory_proc()
RETURNS TRIGGER
AS
$BODY$
BEGIN
IF NEW.permanent = 'YES' THEN
INSERT INTO cms_inventory(id, version, scheme_id, register_num, cms_reference, sensor_id)
VALUES (NEW.id, 0, NEW.scheme_id, NEW.register_num, NEW.cms_reference, NEW.sensor_id);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
ERROR: syntax error at or near "IF"
LINE 9: IF NEW.permanent ='YES' THEN
While it is working in online compiler

Postgres pass refcursor output of a procedure to another procedure

I have this stored procedure T1 which returns a refcursor.
create procedure t1(inout rr refcursor)
as $$
begin
open rr for select 'receiving it';
end;
$$ language plpgsql;
Another stored procedure t_main calls T1 and it needs to return the same refcursor result set of T1.
create or replace procedure t_main(inout rc refcursor)
as
$$
declare
rc_in refcursor;
begin
call t1(rc_in);
rc := rc_in;
end;
$$
language plpgsql;
The part rc := rc_in; is not passing the refcursor rc_in to rc. When I execute t_main, I get error saying cursor 'rc' does not exist.
Thanks in advance.
The solution is as below
create or replace procedure t_main(inout rc refcursor)
as
$$
begin
call t1(rc );
end;
$$
language plpgsql;
Using the same refcursor parameter of the main procedure in the dependent procedure solved the issue.

Use a variable in a postgresql execute statement

I am trying to have a parameter when creating a trigger function.
I have been trying to use this code:
DO $DO$
BEGIN
EXECUTE format($TRIGGER$
CREATE OR REPLACE FUNCTION my_schema.my_trigger_fcn() RETURNS trigger AS
$BODY$
DECLARE
my_geom geometry(MultiPoint,%1$s);
BEGIN
my_geom = st_collect(NEW.situation_geometry)::geometry(MultiPoint,%$1s);
NEW.geometry = my_geom;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
$TRIGGER$, :SRID);
END
$DO$;
and trying to run this code with psql -v SRID=2056 -f myfile.
But I get a syntax error.
I have also tried the SQL execute command, but prepared statements are not allowed to create trigger function.
Any idea?
SOLUTION:
Thanks to #Pavel Stehule, here is the code that works:
SELECT set_config('my.srid', :SRID::text, false);
DO $DO$
BEGIN
EXECUTE format($TRIGGER$
CREATE OR REPLACE FUNCTION qgep_od.my_trigger_fcn() RETURNS trigger AS
$BODY$
DECLARE
my_geom geometry(MultiPoint,%1$s);
BEGIN
my_geom = st_collect(NEW.situation_geometry)::geometry(MultiPoint,%1$s);
NEW.geometry = my_geom;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
$TRIGGER$, current_setting('my.srid'));
END
$DO$;
You cannot to use psql variables inside any SQL string. The string is body of DO command too. You can use session variables:
\set myvar xxx
select set_config('my.myvar', :'myvar', false);
do $$
begin
execute format('create or replace function fx() returns void as $_$begin raise notice %L; end$_$ language plpgsql', current_setting('my.myvar'));
end;
$$;
postgres=# \sf fx
CREATE OR REPLACE FUNCTION public.fx()
RETURNS void
LANGUAGE plpgsql
AS $function$begin raise notice 'xxx'; end$function$
Other possibility is do this replacement before psql - you can use sed

Trigger to update a column on update

I have written this function and this trigger:
CREATE OR REPLACE FUNCTION test4() RETURNS TRIGGER AS $BODY$
BEGIN
UPDATE "cf"
SET vol=(area*alt);
RETURN null;
END;
$BODY$
LANGUAGE plpgsql
trigger
CREATE TRIGGER trig_upd
AFTER OR UPDATE ON "cf"
FOR EACH ROW
EXECUTE PROCEDURE test4();
I have tested my function and it's ok. So I created the trigger, but I can't insert any value in the table "cf" because the system crashes.
If you want to update the value of vol for each modified row, don't use update, just assign the value. For this to work you also need to define the trigger as a before trigger:
CREATE OR REPLACE FUNCTION test4() RETURNS TRIGGER
AS
$BODY$
BEGIN
new.vol := new.area * new.alt;
RETURN new;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER trig_upd
BEFORE UPDATE ON "cf" --<< note the BEFORE!
FOR EACH ROW
EXECUTE PROCEDURE test4()

How should I extract duplicated logic in a Postgres function?

I have a Postgres function with a lot of duplicated logic. If I were writing this in, say, Ruby, I would extract the duplicated logic into a few private helper methods. But there doesn't seem to be an equivalent of "private methods" in Postgres.
Original Function
CREATE OR REPLACE FUNCTION drop_create_idx_constraint(in_operation varchar, in_table_name_or_all_option varchar) RETURNS integer AS $$
DECLARE
cur_drop_for_specific_tab CURSOR (tab_name varchar) IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
cur_drop_for_all_tab CURSOR IS SELECT drop_stmt FROM table_indexes;
cur_create_for_specific_tab CURSOR (tab_name varchar) IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
cur_create_for_all_tab CURSOR IS SELECT recreate_stmt FROM table_indexes;
BEGIN
IF upper(in_operation) = 'DROP' THEN
IF upper(in_table_name_or_all_option) ='ALL' THEN
FOR table_record IN cur_drop_for_all_tab LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
ELSE
FOR table_record IN cur_drop_for_specific_tab(in_table_name_or_all_option) LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
END IF;
ELSIF upper(in_operation) = 'CREATE' THEN
IF upper(in_table_name_or_all_option) ='ALL' THEN
FOR table_record IN cur_create_for_all_tab LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
ELSE
FOR table_record IN cur_create_for_specific_tab(in_table_name_or_all_option) LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
END IF;
END IF;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Refactored Function(s)
CREATE OR REPLACE FUNCTION execute_recreate_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
BEGIN
FOR table_record IN input_cursor LOOP
EXECUTE table_record.recreate_stmt;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION execute_drop_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
BEGIN
FOR table_record IN input_cursor LOOP
EXECUTE table_record.drop_stmt;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION drop_indexes_and_constraints(table_name_to_drop varchar) RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION drop_all_indexes_and_constraints() RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes;
SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION recreate_indexes_and_constraints(table_name_to_recreate varchar) RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_recreate;
SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION recreate_all_indexes_and_constraints() RETURNS integer AS $$
DECLARE
indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes;
SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;
I believe the underlying problem with my refactor is that the helper functions, execute_recreate_stmt_from_records and execute_drop_stmt_from_records, are way too powerful to be publicly accessible, especially since Heroku (which hosts this DB) only allows one DB user. Of course, if there are other problems with the above refactor, feel free to point them out.
You can reach separation by moving "private" procedures into a new schema, limiting access to it. Then use a SECURITY DEFINER to allow calls to "private" functions.
Although, this will be hard to achieve if you are limited to a single user by your hosting service.
Example:
CREATE USER app_user;
CREATE USER private_user;
GRANT ALL ON DATABASE my_database TO app_user;
GRANT CONNECT, CREATE ON DATABASE my_database TO private_user;
-- With private_user:
CREATE SCHEMA private;
CREATE OR REPLACE FUNCTION private.test_func1()
RETURNS integer AS
$BODY$
BEGIN
RETURN 123;
END
$BODY$
LANGUAGE plpgsql STABLE
COST 100;
CREATE OR REPLACE FUNCTION public.my_function_1()
RETURNS integer AS
$BODY$
DECLARE
BEGIN
RETURN private.test_func1();
END
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;
-- With app_user:
SELECT private.test_func1(); -- ERROR: permission denied for schema private
SELECT my_function_1(); -- Returns 123