Cannot drop Postgres trigger function - postgresql

Code:
DROP function IF EXISTS update_rarity;
CREATE OR REPLACE FUNCTION update_rarity() RETURNS trigger AS $$
BEGIN
if (((select count(raresa) FROM Carta WHERE c.raresa LIKE '%Legendary%')*100 /(select count(raresa) FROM Carta)) <> 17 ) then
INSERT INTO Warnings(affected_table,error_message,date,user)
VALUES ('Carta' ,'Proporcions de raresa no respectades: Legendary la proporció actual és '|| (select count(raresa) FROM Carta WHERE raresa LIKE '%Legendary%') || 'quan hauria de ser 3', CURRENT_DATE, CURRENT_USER);
end if;
return null;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS cards ON Carta;
CREATE OR REPLACE TRIGGER cards
AFTER INSERT OR UPDATE ON Carta
FOR EACH ROW
EXECUTE FUNCTION update_rarity();
UPDATE carta SET nom ='Ral Roachs Rascals' ,raresa='Proletari' WHERE nom = 'Rascals';
ERROR: syntax error in or near of «;»
LINE 1: DROP function IF EXISTS update_rarity;
^
SQL state: 42601
Character: 38

The possibility to omit the function signature for a DROP FUNCTION was introduced in Postgres 10. In your outdated and unsupported Postgres version, you need to include the parameter list (which is empty for a trigger function):
DROP function IF EXISTS update_rarity();
^-- this

Related

Adding new column while CREATE TABLE event trigger fired

The goal :
Adding some columns and constraints while creating some tables when the table name begin by "T_R_..."
The code :
CREATE OR REPLACE FUNCTION complete_table()
RETURNS event_trigger
AS
$$
DECLARE
alter_query TEXT;
r RECORD;
BEGIN
IF tg_tag = 'CREATE TABLE'
THEN
r := pg_event_trigger_ddl_commands();
IF r.object_identity LIKE '%.T?_R?_%' ESCAPE '?'
THEN
alter_query = format('ALTER TABLE %s ADD %s_CODE CHAR(16) CONSTRAINT UK_%S_CODE UNIQUE;',
r.object_identity, RIGHT(r.object_identity, 3), RIGHT(r.object_identity, 3));
EXECUTE alter_query;
END IF;
END IF;
END;
$$ LANGUAGE plpgsql;
For the "procedure"... ANd now the trigger :
CREATE EVENT TRIGGER catch_table
ON ddl_command_end
EXECUTE PROCEDURE complete_table();
And finally the test :
CREATE TABLE public.T_R_ABC (ABC_ID INT PRIMARY KEY);
Now, the trouble :
ERROR: ERREUR: la requête « SELECT pg_event_trigger_ddl_commands() » a renvoyé plus d'une ligne
CONTEXT: fonction PL/pgSQL complete_table(), ligne 8 à affectation
Which in english is something like : The query ... returns more than one row ... line 8 when affecting
The function pg_event_trigger_dll_commands() returns several rows: the table itself and the index. You are getting the error that you're getting because you're attempting to assign several rows to a single record. You need to iterate through them and check to make sure you're looking at object_type = 'table'. Also the check for the start of the table name should be case insensitive (ILIKE vs LIKE) and within the format function UK_%S_CODE needs to be UK_%s_CODE (lowercase s):
CREATE OR REPLACE FUNCTION complete_table()
RETURNS event_trigger
LANGUAGE plpgsql
AS $function$
DECLARE
alter_query TEXT;
r RECORD;
BEGIN
IF tg_tag = 'CREATE TABLE'
THEN
FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
LOOP
IF r.object_type = 'table' and r.object_identity ILIKE '%.T?_R?_%' ESCAPE '?'
THEN
alter_query = format('ALTER TABLE %s ADD %s_CODE CHAR(16) CONSTRAINT UK_%s_CODE UNIQUE;',
r.object_identity, RIGHT(r.object_identity, 3), RIGHT(r.object_identity, 3));
EXECUTE alter_query;
END IF;
END LOOP;
END IF;
END;
$function$;

PG DDL event trigger does not work properly

I am trying to intercept a CREATE TABLE by an event trigger in PostgreSQL, to forbid the creation of table that does not comply to some naming rules. My code is as follow:
CREATE OR REPLACE FUNCTION e_ddl_create_table_func()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT *
FROM pg_event_trigger_ddl_commands()
WHERE command_tag in ('CREATE TABLE')
LOOP
if NOT obj.object_identity LIKE 't?_%' ESCAPE '?'
THEN
raise EXCEPTION 'The table name must begin with t_';
end if;
END LOOP;
END;
$$;
CREATE EVENT TRIGGER trg_create_table ON ddl_command_end
WHEN TAG IN ('CREATE TABLE')
EXECUTE PROCEDURE e_ddl_create_table_func();
When I try with:
CREATE TABLE t_toto3 (i INT)
I have systematically the following error:
ERROR: The table name must begin with t_
CONTEXT: fonction PL/pgSQL e_ddl_create_table_func(), ligne 11 à RAISE
What am I missing ?
Per the docs, object_identity is schema-qualified. It will be coming in as 'public.t_toto3' in your example (unless you have a very nonstandard setup with some other default schema); you can get only the table component by passing it through parse_ident() and extracting the 2nd item. (Note the extra parens around the final parse_ident() so that the array lookup is parsed correctly.)
testdb=# select 'public.t_toto3' LIKE 't?_%' ESCAPE '?';
?column?
----------
f
(1 row)
testdb=# select parse_ident('public.t_toto3');
parse_ident
------------------
{public,t_toto3}
(1 row)
testdb=# select (parse_ident('public.t_toto3'))[2] LIKE 't?_%' ESCAPE '?';
?column?
----------
t
(1 row)
yes it works with parse_ident. The solution is :
CREATE OR REPLACE FUNCTION e_ddl_create_table_func()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT *
FROM pg_event_trigger_ddl_commands()
WHERE command_tag in ('CREATE TABLE')
LOOP
if NOT (parse_ident(obj.object_identity))[2] LIKE 't?_%' ESCAPE '?'
THEN
raise EXCEPTION 'The table name must begin with t_';
end if;
END LOOP;
END;
$$;
CREATE EVENT TRIGGER trg_create_table ON ddl_command_end
WHEN TAG IN ('CREATE TABLE')
EXECUTE PROCEDURE e_ddl_create_table_func();

How to implement execute command inside for loop - postgres, dbeaver

I am new to postgres. I need to create a function that will take a list of all the tables in the database whose names are stored in one table and then delete the records of all the tables that are older than x days and have a certain row_status. Some tables do not have a row_status column.
I get an error when I try to save a written function in dbeaver -> ERROR: syntax error at or near "||"
create function delete_old_records1(day1 int, row_status1 character default null, row_status2 character default null)
returns void
language plpgsql
as $$
declare
c all_tables1%rowtype;
begin
for c in select * from all_tables1 loop
if exists(SELECT column_name FROM information_schema.columns
WHERE table_schema = 'yard_kondor' AND table_name =c.table_name AND column_name = 'row_status') then
execute 'delete from '||c.table_name||' where row_create_datetime>current_date-day1+1 and
row_status in (coalesce(row_status1,''P''), row_status2)';
else
execute 'delete from '||c.table_name||' where row_create_datetime>current_date-day1+1';
raise notice 'Table '||c.table_name||' does not have row_status column';
end if;
end loop;
return;
commit;
end;
$$
Your immediate problem is this line:
raise notice 'Table '||c.table_name||' does not have row_status column';
That should be:
raise notice 'Table % does not have row_status column', c.table_name;
However, your function could be improved a bit. In general it is highly recommended to use format() to generate dynamic SQL to properly deal with identifiers. You also can't commit in a function. If you really need that, use a procedure.
create function delete_old_records1(day1 int, row_status1 character default null, row_status2 character default null)
returns void
language plpgsql
as $$
declare
c all_tables1%rowtype;
begin
for c in select * from all_tables1
loop
if exists (SELECT column_name FROM information_schema.columns
WHERE table_schema = 'yard_kondor'
AND table_name = c.table_name
AND column_name = 'row_status') then
execute format('delete from %I
where row_create_datetime > current_date - %J + 1
and row_status in (coalesce(row_status1,%L), row_status2)', c.table_name, day1, 'P');
else
execute format('delete from %I where row_create_datetime > current_date - day1 + 1', c.table_name);
raise notice 'Table % does not have row_status column', c.table_name;
end if;
end loop;
return;
-- you can't commit in a function
end;
$$
Thank you for answer. Now I'm able to save function, but currently I have a problem with running the function.
I started the function with:
DO $$ BEGIN
PERFORM "delete_old_records1"(31,'P','N');
END $$;
I started script with ALT+X (also tried select delete_old_records1(31,'P','N');) and have this error:
SQL Error [42703]: ERROR: column "day1" does not exist
Where: PL/pgSQL function delete_old_records1(integer,character,character) line 11 at EXECUTE statement
SQL statement "SELECT "delete_old_records1"(31,'P','N')"
PL/pgSQL function inline_code_block line 2 at PERFORM

PostgreSQL Error Codes, set a custom error code

I'm translating a Oracle trigger to Postgres; I have this so far translated in postgres
DROP TRIGGER IF EXISTS trg_test_biud ON mytable CASCADE;
CREATE OR REPLACE FUNCTION trigger_fct_trg_test_biud() RETURNS trigger AS $BODY$
DECLARE
id_ double precision := NULL;
hour_ varchar(10) := NULL;
BEGIN
/* INSERT */
IF TG_OP = 'INSERT' THEN
BEGIN
select nextval('myschema.id_audit_mytable_seq') into id_;
SELECT TO_CHAR(current_timestamp, 'HH24:MI:SS') INTO hour_;
INSERT INTO myschema.audit_mytable(id, id_mytable, user_name, event, myhour, hour, geometry)
VALUES (id_, NEW.code, NEW.user_name, 'INSERT', LOCALTIMESTAMP, hour_, NEW.GEOMETRY);
RETURN NEW;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION '%', 'Error when insert into audit_mytable: ' || sqlerrm USING ERRCODE = '-20000';
END;
END IF;
END
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_test_biud
BEFORE INSERT OR UPDATE OR DELETE ON myschema.mytable FOR EACH ROW
EXECUTE PROCEDURE trigger_fct_trg_test_biud();
When the exception is raised, I get this error:
ERROR: unrecognized exception condition «-20000»
SQL state: 42704
Does this has to do with the fact that in Oracle the 'custom error code' is a negative number? postgres does not recognize this? I checked this page, but it says nothing about negative numbers: https://www.postgresql.org/docs/current/static/errcodes-appendix.html
The Oracle number -20000 is not an SQLSTATE, but a proprietary error code.
You have to specify one of the 5-character SQLSTATEs defined in appendix A of the documentation.

How to dynamically copy tables from information schema inside a trigger function

I have an insert trigger function in which NEW.schema_name references a schema. I want to dynamically copy the tables found inside that schema ('foobaz','barbaz') as 'foo' and 'bar'. I then can perform queries without dynamic sql.
How can I create a function or simply copy/paste the same block of code to achive that.
EDIT :
I cannot get that dynamic query to work.
The part inside the WITH statement is working.
Not the bottom 'execute' part. I do not know if it is a syntax problem, or bad cast or whatever constraint there is in pgsql that makes it not working.
WITH info_schema_subset_table as (SELECT table_schema, table_name,
array_to_string((regexp_split_to_array(table_name,'_'))[4:array_length(regexp_split_to_array(table_name,'_'),1)-1] as new_table
FROM information_schema.tables
where table_schema = "schema_searched"
ORDER BY new_table ASC)
EXECUTE 'CREATE TABLE $2 as (SELECT * FROM $1)'
USING info_schema_subset_table.table_schema || '.' ||info_schema_subset_table.table_name,info_schema_subset_table.new_table;
EDIT 2
... Broken code removed...
In the code below, in which I'm unsure if the syntax is right, I get the following from the trigger
Provider errors:
PostGIS error while adding features: ERREUR: l'opérateur n'existe pas : record ~~ unknown
LINE 1: SELECT old_table LIKE '%ens%'
^
HINT: Aucun opérateur ne correspond au nom donné et aux types d'arguments.
Vous devez ajouter des conversions explicites de type.
QUERY: SELECT old_table LIKE '%ens%'
CONTEXT: fonction PL/pgsql validation_sio.afi_validation_sio(), ligne 18 à CASE
EDIT 3 :
CREATE OR REPLACE FUNCTION foo.foo()
RETURNS TRIGGER AS
$BODY$
DECLARE
old_table record;
new_table record;
dynamic_query text;
BEGIN
IF TG_OP = 'INSERT'
THEN
FOR old_table IN SELECT table_schema|| '.' ||table_name
FROM information_schema.tables
where table_schema = NEW.nom_schema
LOOP
CASE
WHEN
old_table LIKE '%ens%' THEN
new_table := concat('SIT_',array_to_string((regexp_split_to_array(info_schema.old_table,'_'))[4:array_length(regexp_split_to_array(info_schema.old_table,'_'),1)-1],'_'));
ELSE
new_table := concat('SID_',array_to_string((regexp_split_to_array(info_schema.old_table,'_'))[4:array_length(regexp_split_to_array(info_schema.old_table,'_'),1)-1],'_'));
END CASE;
dynamic_query := format('SELECT * FROM' || old_table ||);
EXECUTE dynamic_query
INTO new_table;
END LOOP;
RETURN NEW;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER foo
AFTER INSERT ON validation.validationfoo
FOR EACH ROW EXECUTE PROCEDURE foo.foo();
I've reformatted your trigger function a bit and changed a few things, see if this works.
CREATE OR REPLACE FUNCTION foo.foo()
RETURNS TRIGGER AS
$BODY$
DECLARE
old_table record;
new_table record;
dynamic_query text;
BEGIN
IF TG_OP = 'INSERT' THEN
FOR old_table IN
SELECT table_schema || '.' || table_name AS old_table_name
FROM information_schema.tables
WHERE table_schema = NEW.nom_schema
LOOP
new_table := concat(CASE WHEN old_table.old_table_name LIKE '%ens%' THEN 'SIT_' ELSE 'SID_' END,array_to_string((regexp_split_to_array(info_schema.old_table,'_'))[4:array_length(regexp_split_to_array(info_schema.old_table,'_'),1)-1],'_'));
dynamic_query := 'CREATE TABLE ' || new_table || ' AS SELECT * FROM ' || old_table.old_table_name;
EXECUTE dynamic_query;
END LOOP;
RETURN NEW;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
So the main things:
old_table is a record, so your comparison of it to a string with LIKE was failing. You need to use the field name. So I gave your field a name, and used that field name in the LIKE comparison.
Changed the new_table assignment to put the CASE statement only on the one item that changes, to make the difference more obvious and the code more concise. Mind you, I don't know if the rest of that line is actually valid, I just left it as is.
Changed the creation dynamic_query. As I said in the comment, the format function was being used incorrectly, so I just went with standard string concatenation instead.
Changed dynamic_query's SQL to what I think you actually want it to do. You want it to copy the content of the table to a new table, right? So that will do it.
You cannot have EXECUTE inside an SQL statement, it is a PL/pgSQL statement.
Loop through the tables and issue one EXECUTE for each.
Mind that you cannot have a schema or table name as a parameter with USING, because these names need to be known at parse time.
Use the format function to construct your dynamic statement so you can avoid SQL injection by users who maliciously create tables with weird names.