insertinto trigtest values(1, 'foo'); select * from trigtest; update trigtest set f2 = f2 || 'bar'; select * from trigtest; deletefrom trigtest; select * from trigtest;
-- Also check what happens when such a trigger runs before or after others create function f1_times_10() returns triggeras
$$ begin new.f1 := new.f1 * 10; return new; end $$ language plpgsql;
insertinto trigtest values(1, 'foo'); select * from trigtest; update trigtest set f2 = f2 || 'bar'; select * from trigtest; deletefrom trigtest; select * from trigtest;
droptrigger trigger_alpha on trigtest;
insertinto trigtest values(1, 'foo'); select * from trigtest; update trigtest set f2 = f2 || 'bar'; select * from trigtest; deletefrom trigtest; select * from trigtest;
droptable trigtest;
-- Check behavior with an implicit column default, too (bug #16644) createtable trigtest (
a integer,
b bool defaulttruenotnull,
c text default'xyzzy'notnull);
CREATETRIGGER after_ins_stmt_trig AFTER INSERTON main_table FOREACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
-- -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified, -- CREATE TRIGGER should default to 'FOR EACH STATEMENT' -- CREATETRIGGER after_upd_stmt_trig AFTER UPDATEON main_table
EXECUTE PROCEDURE trigger_func('after_upd_stmt');
-- Both insert and update statement level triggers (before and after) should -- fire. Doesn't fire UPDATE before trigger, but only because one isn't -- defined. INSERTINTO main_table (a, b) VALUES (5, 10) ON CONFLICT (a)
DO UPDATESET b = EXCLUDED.b;
CREATETRIGGER after_upd_row_trig AFTER UPDATEON main_table FOREACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERTINTO main_table DEFAULTVALUES;
UPDATE main_table SET a = a + 1WHERE b < 30; -- UPDATE that effects zero rows should still call per-statement trigger UPDATE main_table SET a = a + 2WHERE b > 100;
-- constraint now unneeded ALTERTABLE main_table DROPCONSTRAINT main_table_a_key;
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin; 3040 5060
\.
SELECT * FROM main_table ORDERBY a, b;
-- Test comments
COMMENT ONTRIGGER no_such_trigger ON main_table IS'wrong';
COMMENT ONTRIGGER before_ins_stmt_trig ON main_table IS'right';
COMMENT ONTRIGGER before_ins_stmt_trig ON main_table ISNULL;
-- -- test triggers with WHEN clause --
CREATETRIGGER modified_a BEFOREUPDATE OF a ON main_table FOREACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('modified_a'); CREATETRIGGER modified_any BEFOREUPDATE OF a ON main_table FOREACH ROW WHEN (OLD.* ISDISTINCTFROM NEW.*) EXECUTE PROCEDURE trigger_func('modified_any'); CREATETRIGGER insert_a AFTER INSERTON main_table FOREACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a'); CREATETRIGGER delete_a AFTER DELETEON main_table FOREACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a'); CREATETRIGGER insert_when BEFOREINSERTON main_table FOREACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when'); CREATETRIGGER delete_when AFTER DELETEON main_table FOREACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when'); SELECT trigger_name, event_manipulation, event_object_schema, event_object_table,
action_order, action_condition, action_orientation, action_timing,
action_reference_old_table, action_reference_new_table FROM information_schema.triggers WHERE event_object_table IN ('main_table') ORDERBY trigger_name COLLATE"C", 2; INSERTINTO main_table (a) VALUES (123), (456);
COPY main_table FROM stdin; 123999 456999
\. DELETEFROM main_table WHERE a IN (123, 456); UPDATE main_table SET a = 50, b = 60; SELECT * FROM main_table ORDERBY a, b; SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; SELECT pg_get_triggerdef(oid, false) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any';
-- Test RENAME TRIGGER ALTERTRIGGER modified_a ON main_table RENAMETO modified_modified_a; SELECT count(*) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a'; SELECT count(*) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_modified_a';
DROPTRIGGER modified_modified_a ON main_table; DROPTRIGGER modified_any ON main_table; DROPTRIGGER insert_a ON main_table; DROPTRIGGER delete_a ON main_table; DROPTRIGGER insert_when ON main_table; DROPTRIGGER delete_when ON main_table;
-- Test WHEN condition accessing system columns. createtable table_with_oids(a int); insertinto table_with_oids values (1); createtrigger oid_unchanged_trig after updateon table_with_oids foreach row when (new.tableoid = old.tableoid AND new.tableoid <> 0)
execute procedure trigger_func('after_upd_oid_unchanged'); update table_with_oids set a = a + 1; droptable table_with_oids;
-- Test column-level triggers DROPTRIGGER after_upd_row_trig ON main_table;
CREATETRIGGER before_upd_a_row_trig BEFOREUPDATE OF a ON main_table FOREACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row'); CREATETRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table FOREACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row'); CREATETRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table FOREACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
CREATETRIGGER before_upd_a_stmt_trig BEFOREUPDATE OF a ON main_table FOREACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt'); CREATETRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table FOREACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt');
SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass ANDtgname = 'after_upd_a_b_row_trig';
UPDATE main_table SET a = 50; UPDATE main_table SET b = 10;
-- -- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN --
CREATETABLE some_t (some_col boolean NOTNULL); CREATE FUNCTION dummy_update_func() RETURNS triggerAS $$
BEGIN
RAISE NOTICE 'dummy_update_func(%) called: action = %, old = %, new = %',
TG_ARGV[0], TG_OP, OLD, NEW; RETURN NEW;
END;
$$ LANGUAGE plpgsql; CREATETRIGGER some_trig_before BEFOREUPDATEON some_t FOREACH ROW
EXECUTE PROCEDURE dummy_update_func('before'); CREATETRIGGER some_trig_aftera AFTER UPDATEON some_t FOREACH ROW WHEN (NOT OLD.some_col AND NEW.some_col)
EXECUTE PROCEDURE dummy_update_func('aftera'); CREATETRIGGER some_trig_afterb AFTER UPDATEON some_t FOREACH ROW WHEN (NOT NEW.some_col)
EXECUTE PROCEDURE dummy_update_func('afterb'); INSERTINTO some_t VALUES (TRUE); UPDATE some_t SET some_col = TRUE; UPDATE some_t SET some_col = FALSE; UPDATE some_t SET some_col = TRUE; DROPTABLE some_t;
-- bogus cases CREATETRIGGER error_upd_and_col BEFOREUPDATEORUPDATE OF a ON main_table FOREACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col'); CREATETRIGGER error_upd_a_a BEFOREUPDATE OF a, a ON main_table FOREACH ROW EXECUTE PROCEDURE trigger_func('error_upd_a_a'); CREATETRIGGER error_ins_a BEFOREINSERT OF a ON main_table FOREACH ROW EXECUTE PROCEDURE trigger_func('error_ins_a'); CREATETRIGGER error_ins_when BEFOREINSERTORUPDATEON main_table FOREACH ROW WHEN (OLD.a <> NEW.a)
EXECUTE PROCEDURE trigger_func('error_ins_old'); CREATETRIGGER error_del_when BEFOREDELETEORUPDATEON main_table FOREACH ROW WHEN (OLD.a <> NEW.a)
EXECUTE PROCEDURE trigger_func('error_del_new'); CREATETRIGGER error_del_when BEFOREINSERTORUPDATEON main_table FOREACH ROW WHEN (NEW.tableoid <> 0)
EXECUTE PROCEDURE trigger_func('error_when_sys_column'); CREATETRIGGER error_stmt_when BEFOREUPDATE OF a ON main_table FOREACH STATEMENT WHEN (OLD.* ISDISTINCTFROM NEW.*)
EXECUTE PROCEDURE trigger_func('error_stmt_when');
-- check dependency restrictions ALTERTABLE main_table DROPCOLUMN b; -- this should succeed, but we'll roll it back to keep the triggers around
begin; DROPTRIGGER after_upd_a_b_row_trig ON main_table; DROPTRIGGER after_upd_b_row_trig ON main_table; DROPTRIGGER after_upd_b_stmt_trig ON main_table; ALTERTABLE main_table DROPCOLUMN b;
rollback;
-- Test enable/disable triggers
createtable trigtest (i serial primarykey); -- test that disabling RI triggers works createtable trigtest2 (i intreferences trigtest(i) ondeletecascade);
create function trigtest() returns triggeras $$
begin
raise notice '% % % %', TG_TABLE_NAME, TG_OP, TG_WHEN, TG_LEVEL; return new;
end;$$ language plpgsql;
insertinto trigtest defaultvalues; altertable trigtest disable trigger trigtest_b_row_tg; insertinto trigtest defaultvalues; altertable trigtest disable trigger user; insertinto trigtest defaultvalues; altertable trigtest enable trigger trigtest_a_stmt_tg; insertinto trigtest defaultvalues; set session_replication_role = replica; insertinto trigtest defaultvalues; -- does not trigger altertable trigtest enable always trigger trigtest_a_stmt_tg; insertinto trigtest defaultvalues; -- now it does
reset session_replication_role; insertinto trigtest2 values(1); insertinto trigtest2 values(2); deletefrom trigtest where i=2; select * from trigtest2; altertable trigtest disable triggerall; deletefrom trigtest where i=1; select * from trigtest2; -- ensure we still insert, even when all triggers are disabled insertinto trigtest defaultvalues; select * from trigtest; droptable trigtest2; droptable trigtest;
-- dump trigger data CREATETABLE trigger_test (
i int,
v varchar
);
CREATEORREPLACE FUNCTION trigger_data() RETURNS trigger
LANGUAGE plpgsql AS $$
declare
argstr text;
relid text;
begin
relid := TG_relid::regclass;
-- plpgsql can't discover its trigger data in a hash like perl and python -- can, or by a sort of reflection like tcl can, -- so we have to hard code the names.
raise NOTICE 'TG_NAME: %', TG_name;
raise NOTICE 'TG_WHEN: %', TG_when;
raise NOTICE 'TG_LEVEL: %', TG_level;
raise NOTICE 'TG_OP: %', TG_op;
raise NOTICE 'TG_RELID::regclass: %', relid;
raise NOTICE 'TG_RELNAME: %', TG_relname;
raise NOTICE 'TG_TABLE_NAME: %', TG_table_name;
raise NOTICE 'TG_TABLE_SCHEMA: %', TG_table_schema;
raise NOTICE 'TG_NARGS: %', TG_nargs;
argstr := '['; for i in0 .. TG_nargs - 1loop if i > 0then
argstr := argstr || ', ';
end if;
argstr := argstr || TG_argv[i];
end loop;
argstr := argstr || ']';
raise NOTICE 'TG_ARGV: %', argstr;
if TG_OP != 'INSERT'then
raise NOTICE 'OLD: %', OLD;
end if;
if TG_OP != 'DELETE'then
raise NOTICE 'NEW: %', NEW;
end if;
if TG_OP = 'DELETE'then return OLD; else return NEW;
end if;
-- this is the obvious (and wrong...) way to compare rows CREATE FUNCTION mytrigger() RETURNS trigger LANGUAGE plpgsql as $$
begin if row(old.*) = row(new.*) then
raise notice 'row % not changed', new.f1; else
raise notice 'row % changed', new.f1;
end if; return new;
end$$;
CREATETRIGGER t BEFOREUPDATEON trigger_test FOREACH ROW EXECUTE PROCEDURE mytrigger();
UPDATE trigger_test SET f3 = 'bar'; UPDATE trigger_test SET f3 = NULL; -- this demonstrates that the above isn't really working as desired: UPDATE trigger_test SET f3 = NULL;
-- the right way when considering nulls is CREATEORREPLACE FUNCTION mytrigger() RETURNS trigger LANGUAGE plpgsql as $$
begin if row(old.*) isdistinctfrom row(new.*) then
raise notice 'row % changed', new.f1; else
raise notice 'row % not changed', new.f1;
end if; return new;
end$$;
UPDATE trigger_test SET f3 = 'bar'; UPDATE trigger_test SET f3 = NULL; UPDATE trigger_test SET f3 = NULL;
DROPTABLE trigger_test;
DROP FUNCTION mytrigger();
-- Test snapshot management in serializable transactions involving triggers -- per bug report in 6bc73d4c0910042358k3d1adff3qa36f8df75198ecea@mail.gmail.com CREATE FUNCTION serializable_update_trig() RETURNS trigger LANGUAGE plpgsql AS
$$ declare
rec record;
begin
new.description = 'updated in trigger'; return new;
end;
$$;
CREATETABLE serializable_update_tab (
id int,
filler text,
description text
);
INSERTINTO serializable_update_tab SELECT a, repeat('xyzxz', 100), 'new' FROM generate_series(1, 50) a;
BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE serializable_update_tab SET description = 'no no', id = 1WHERE id = 1; COMMIT; SELECT description FROM serializable_update_tab WHERE id = 1; DROPTABLE serializable_update_tab;
-- minimal update trigger
CREATETABLE min_updates_test (
f1 text,
f2 int,
f3 int);
UPDATE min_updates_test SET f3 = 2WHERE f3 isnull;
\set QUIET true
SELECT * FROM min_updates_test;
DROPTABLE min_updates_test;
-- -- Test triggers on views --
CREATE VIEW main_view ASSELECT a, b FROM main_table;
-- VIEW trigger function CREATEORREPLACE FUNCTION view_trigger() RETURNS trigger
LANGUAGE plpgsql AS $$ declare
argstr text := '';
begin for i in0 .. TG_nargs - 1loop if i > 0then
argstr := argstr || ', ';
end if;
argstr := argstr || TG_argv[i];
end loop;
if TG_LEVEL = 'ROW'then if TG_OP = 'INSERT'then
raise NOTICE 'NEW: %', NEW; INSERTINTO main_table VALUES (NEW.a, NEW.b); RETURN NEW;
end if;
if TG_OP = 'UPDATE'then
raise NOTICE 'OLD: %, NEW: %', OLD, NEW; UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b; ifNOT FOUND thenRETURNNULL; end if; RETURN NEW;
end if;
if TG_OP = 'DELETE'then
raise NOTICE 'OLD: %', OLD; DELETEFROM main_table WHERE a = OLD.a AND b = OLD.b; ifNOT FOUND thenRETURNNULL; end if; RETURN OLD;
end if;
end if;
RETURNNULL;
end;
$$;
-- Before row triggers aren't allowed on views CREATETRIGGER invalid_trig BEFOREINSERTON main_view FOREACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
-- Don't support WHEN clauses with INSTEAD OF triggers CREATETRIGGER invalid_trig INSTEAD OF UPDATEON main_view FOREACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE view_trigger('instead_of_upd');
-- Don't support column-level INSTEAD OF triggers CREATETRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view FOREACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd');
-- Don't support statement-level INSTEAD OF triggers CREATETRIGGER invalid_trig INSTEAD OF UPDATEON main_view
EXECUTE PROCEDURE view_trigger('instead_of_upd');
-- Valid INSTEAD OF triggers CREATETRIGGER instead_of_insert_trig INSTEAD OF INSERTON main_view FOREACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins');
-- Valid AFTER statement VIEW triggers CREATETRIGGER after_ins_stmt_trig AFTER INSERTON main_view FOREACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt');
CREATETRIGGER after_upd_stmt_trig AFTER UPDATEON main_view FOREACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt');
CREATETRIGGER after_del_stmt_trig AFTER DELETEON main_view FOREACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt');
\set QUIET false
-- Insert into view using trigger INSERTINTO main_view VALUES (20, 30); INSERTINTO main_view VALUES (21, 31) RETURNING a, b;
-- Table trigger will prevent updates UPDATE main_view SET b = 31WHERE a = 20; UPDATE main_view SET b = 32WHERE a = 21AND b = 31 RETURNING a, b;
-- Remove table trigger to allow updates DROPTRIGGER before_upd_a_row_trig ON main_table; UPDATE main_view SET b = 31WHERE a = 20; UPDATE main_view SET b = 32WHERE a = 21AND b = 31 RETURNING a, b;
-- Before and after stmt triggers should fire even when no rows are affected UPDATE main_view SET b = 0WHEREfalse;
-- Delete from view using trigger DELETEFROM main_view WHERE a IN (20,21); DELETEFROM main_view WHERE a = 31 RETURNING a, b;
\set QUIET true
-- Describe view should list triggers
\d main_view
-- Test dropping view triggers DROPTRIGGER instead_of_insert_trig ON main_view; DROPTRIGGER instead_of_delete_trig ON main_view;
\d+ main_view DROP VIEW main_view;
-- -- Test triggers on a join view -- CREATETABLE country_table (
country_id serial primarykey,
country_name text uniquenotnull,
continent text notnull
);
CREATETABLE city_table (
city_id serial primarykey,
city_name text notnull,
population bigint,
country_id intreferences country_table
);
CREATE VIEW city_view AS SELECT city_id, city_name, population, country_name, continent FROM city_table ci LEFTJOIN country_table co ON co.country_id = ci.country_id;
CREATE FUNCTION city_insert() RETURNS trigger LANGUAGE plpgsql AS $$ declare
ctry_id int;
begin if NEW.country_name ISNOTNULLthen SELECT country_id, continent INTO ctry_id, NEW.continent FROM country_table WHERE country_name = NEW.country_name; ifNOT FOUND then
raise exception 'No such country: "%"', NEW.country_name;
end if; else
NEW.continent := NULL;
end if;
if NEW.city_id ISNOTNULLthen INSERTINTO city_table VALUES(NEW.city_id, NEW.city_name, NEW.population, ctry_id); else INSERTINTO city_table(city_name, population, country_id) VALUES(NEW.city_name, NEW.population, ctry_id)
RETURNING city_id INTO NEW.city_id;
end if;
CREATE FUNCTION city_delete() RETURNS trigger LANGUAGE plpgsql AS $$
begin DELETEFROM city_table WHERE city_id = OLD.city_id; ifNOT FOUND thenRETURNNULL; end if; RETURN OLD;
end;
$$;
CREATE FUNCTION city_update() RETURNS trigger LANGUAGE plpgsql AS $$ declare
ctry_id int;
begin if NEW.country_name ISDISTINCTFROM OLD.country_name then SELECT country_id, continent INTO ctry_id, NEW.continent FROM country_table WHERE country_name = NEW.country_name; ifNOT FOUND then
raise exception 'No such country: "%"', NEW.country_name;
end if;
UPDATE city_table SET city_name = NEW.city_name,
population = NEW.population,
country_id = ctry_id WHERE city_id = OLD.city_id; else UPDATE city_table SET city_name = NEW.city_name,
population = NEW.population WHERE city_id = OLD.city_id;
NEW.continent := OLD.continent;
end if;
ifNOT FOUND thenRETURNNULL; end if; RETURN NEW;
end;
$$;
-- read-only view with WHERE clause CREATE VIEW european_city_view AS SELECT * FROM city_view WHERE continent = 'Europe'; SELECT count(*) FROM european_city_view;
CREATE FUNCTION no_op_trig_fn() RETURNS trigger LANGUAGE plpgsql AS'begin RETURN NULL; end';
CREATETRIGGER no_op_trig INSTEAD OF INSERTORUPDATEORDELETE ON european_city_view FOREACH ROW EXECUTE PROCEDURE no_op_trig_fn();
\set QUIET false
INSERTINTO european_city_view VALUES (0, 'x', 10000, 'y', 'z'); UPDATE european_city_view SET population = 10000; DELETEFROM european_city_view;
CREATE RULE european_city_update_rule ASONUPDATETO european_city_view
DO INSTEAD UPDATE city_view SET
city_name = NEW.city_name,
population = NEW.population,
country_name = NEW.country_name WHERE city_id = OLD.city_id
RETURNING NEW.*;
CREATE RULE european_city_delete_rule ASONDELETETO european_city_view
DO INSTEAD DELETEFROM city_view WHERE city_id = OLD.city_id RETURNING *;
\set QUIET false
-- INSERT not limited by view's WHERE clause, but UPDATE AND DELETE are INSERTINTO european_city_view(city_name, country_name) VALUES ('Cambridge', 'USA') RETURNING *; UPDATE european_city_view SET country_name = 'UK' WHERE city_name = 'Cambridge'; DELETEFROM european_city_view WHERE city_name = 'Cambridge';
-- UPDATE and DELETE via rule and trigger UPDATE city_view SET country_name = 'UK' WHERE city_name = 'Cambridge' RETURNING *; UPDATE european_city_view SET population = 122800 WHERE city_name = 'Cambridge' RETURNING *; DELETEFROM european_city_view WHERE city_name = 'Cambridge' RETURNING *;
-- join UPDATE test UPDATE city_view v SET population = 599657 FROM city_table ci, country_table co WHERE ci.city_name = 'Washington DC'and co.country_name = 'USA' AND v.city_id = ci.city_id AND v.country_name = co.country_name
RETURNING co.country_id, v.country_name,
v.city_id, v.city_name, v.population;
create function parent_upd_func()
returns trigger language plpgsql as
$$
begin if old.val1 <> new.val1 then
new.val2 = new.val1; deletefrom child where child.aid = new.aid and child.val1 = new.val1;
end if; return new;
end;
$$; createtrigger parent_upd_trig beforeupdateon parent foreach row execute procedure parent_upd_func();
create function parent_del_func()
returns trigger language plpgsql as
$$
begin deletefrom child where aid = old.aid; return old;
end;
$$; createtrigger parent_del_trig beforedeleteon parent foreach row execute procedure parent_del_func();
create function child_ins_func()
returns trigger language plpgsql as
$$
begin update parent set bcnt = bcnt + 1where aid = new.aid; return new;
end;
$$; createtrigger child_ins_trig after inserton child foreach row execute procedure child_ins_func();
create function child_del_func()
returns trigger language plpgsql as
$$
begin update parent set bcnt = bcnt - 1where aid = old.aid; return old;
end;
$$; createtrigger child_del_trig after deleteon child foreach row execute procedure child_del_func();
update parent set val1 = 'b'where aid = 1; -- should fail
merge into parent p using (values (1)) as v(id) on p.aid = v.id when matched thenupdateset val1 = 'b'; -- should fail select * from parent; select * from child;
deletefrom parent where aid = 1; -- should fail
merge into parent p using (values (1)) as v(id) on p.aid = v.id when matched thendelete; -- should fail select * from parent; select * from child;
-- replace the trigger function with one that restarts the deletion after -- having modified a child createorreplace function parent_del_func()
returns trigger language plpgsql as
$$
begin deletefrom child where aid = old.aid; if found then deletefrom parent where aid = old.aid; returnnull; -- cancel outer deletion
end if; return old;
end;
$$;
deletefrom parent where aid = 1; select * from parent; select * from child;
droptable parent, child;
drop function parent_upd_func(); drop function parent_del_func(); drop function child_ins_func(); drop function child_del_func();
-- similar case, but with a self-referencing FK so that parent and child -- rows can be affected by a single operation
create temp table self_ref_trigger (
id intprimarykey,
parent intreferences self_ref_trigger,
data text,
nchildren intnotnulldefault0
);
create function self_ref_trigger_ins_func()
returns trigger language plpgsql as
$$
begin if new.parent isnotnullthen update self_ref_trigger set nchildren = nchildren + 1 where id = new.parent;
end if; return new;
end;
$$; createtrigger self_ref_trigger_ins_trig beforeinserton self_ref_trigger foreach row execute procedure self_ref_trigger_ins_func();
create function self_ref_trigger_del_func()
returns trigger language plpgsql as
$$
begin if old.parent isnotnullthen update self_ref_trigger set nchildren = nchildren - 1 where id = old.parent;
end if; return old;
end;
$$; createtrigger self_ref_trigger_del_trig beforedeleteon self_ref_trigger foreach row execute procedure self_ref_trigger_del_func();
update self_ref_trigger set data = 'root!'where id = 1;
select * from self_ref_trigger;
deletefrom self_ref_trigger;
select * from self_ref_trigger;
droptable self_ref_trigger; drop function self_ref_trigger_ins_func(); drop function self_ref_trigger_del_func();
-- -- Check that statement triggers work correctly even with all children excluded --
createtable stmt_trig_on_empty_upd (a int); createtable stmt_trig_on_empty_upd1 () inherits (stmt_trig_on_empty_upd); create function update_stmt_notice() returns triggeras $$
begin
raise notice 'updating %', TG_TABLE_NAME; returnnull;
end;
$$ language plpgsql; createtrigger before_stmt_trigger beforeupdateon stmt_trig_on_empty_upd
execute procedure update_stmt_notice(); createtrigger before_stmt_trigger beforeupdateon stmt_trig_on_empty_upd1
execute procedure update_stmt_notice();
-- inherited no-op update update stmt_trig_on_empty_upd set a = a wherefalse returning a+1as aa; -- simple no-op update update stmt_trig_on_empty_upd1 set a = a wherefalse returning a+1as aa;
droptable stmt_trig_on_empty_upd cascade; drop function update_stmt_notice();
-- -- Check that index creation (or DDL in general) is prohibited in a trigger --
droptable trigger_ddl_table; drop function trigger_ddl_func();
-- -- Verify behavior of before and after triggers with INSERT...ON CONFLICT -- DO UPDATE -- createtable upsert (keyint4primarykey, color text);
create function upsert_before_func()
returns trigger language plpgsql as
$$
begin if (TG_OP = 'UPDATE') then
raise warning 'before update (old): %', old.*::text;
raise warning 'before update (new): %', new.*::text;
elsif (TG_OP = 'INSERT') then
raise warning 'before insert (new): %', new.*::text; if new.key % 2 = 0then
new.key := new.key + 1;
new.color := new.color || ' trig modified';
raise warning 'before insert (new, modified): %', new.*::text;
end if;
end if; return new;
end;
$$; createtrigger upsert_before_trig beforeinsertorupdateon upsert foreach row execute procedure upsert_before_func();
create function upsert_after_func()
returns trigger language plpgsql as
$$
begin if (TG_OP = 'UPDATE') then
raise warning 'after update (old): %', old.*::text;
raise warning 'after update (new): %', new.*::text;
elsif (TG_OP = 'INSERT') then
raise warning 'after insert (new): %', new.*::text;
end if; returnnull;
end;
$$; createtrigger upsert_after_trig after insertorupdateon upsert foreach row execute procedure upsert_after_func();
insertinto upsert values(1, 'black') on conflict (key) do updateset color = 'updated ' || upsert.color; insertinto upsert values(2, 'red') on conflict (key) do updateset color = 'updated ' || upsert.color; insertinto upsert values(3, 'orange') on conflict (key) do updateset color = 'updated ' || upsert.color; insertinto upsert values(4, 'green') on conflict (key) do updateset color = 'updated ' || upsert.color; insertinto upsert values(5, 'purple') on conflict (key) do updateset color = 'updated ' || upsert.color; insertinto upsert values(6, 'white') on conflict (key) do updateset color = 'updated ' || upsert.color; insertinto upsert values(7, 'pink') on conflict (key) do updateset color = 'updated ' || upsert.color; insertinto upsert values(8, 'yellow') on conflict (key) do updateset color = 'updated ' || upsert.color;
select * from upsert;
droptable upsert; drop function upsert_before_func(); drop function upsert_after_func();
-- -- Verify that triggers with transition tables are not allowed on -- views --
createtable my_table (i int); create view my_view asselect * from my_table; create function my_trigger_function() returns triggeras $$ begin end; $$ language plpgsql; createtrigger my_trigger after updateon my_view referencing old tableas old_table foreach statement execute procedure my_trigger_function(); drop function my_trigger_function(); drop view my_view; droptable my_table;
-- -- Verify cases that are unsupported with partitioned tables -- createtable parted_trig (a int) partition by list (a); create function trigger_nothing() returns trigger
language plpgsql as $$ begin end; $$; createtrigger failed instead of updateon parted_trig foreach row execute procedure trigger_nothing(); createtrigger failed after updateon parted_trig
referencing old tableas old_table foreach row execute procedure trigger_nothing(); droptable parted_trig;
-- -- Verify trigger creation for partitioned tables, and drop behavior -- createtable trigpart (a int, b int) partition by range (a); createtable trigpart1 partition of trigpart forvaluesfrom (0) to (1000); createtrigger trg1 after inserton trigpart foreach row execute procedure trigger_nothing(); createtable trigpart2 partition of trigpart forvaluesfrom (1000) to (2000); createtable trigpart3 (like trigpart); altertable trigpart attach partition trigpart3 forvaluesfrom (2000) to (3000); createtable trigpart4 partition of trigpart forvaluesfrom (3000) to (4000) partition by range (a); createtable trigpart41 partition of trigpart4 forvaluesfrom (3000) to (3500); createtable trigpart42 (like trigpart); altertable trigpart4 attach partition trigpart42 forvaluesfrom (3500) to (4000); select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger where tgrelid::regclass::text like'trigpart%'orderby tgrelid::regclass::text; droptrigger trg1 on trigpart1; -- fail droptrigger trg1 on trigpart2; -- fail droptrigger trg1 on trigpart3; -- fail droptable trigpart2; -- ok, trigger should be gone in that partition select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger where tgrelid::regclass::text like'trigpart%'orderby tgrelid::regclass::text; droptrigger trg1 on trigpart; -- ok, all gone select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger where tgrelid::regclass::text like'trigpart%'orderby tgrelid::regclass::text;
-- check detach behavior createtrigger trg1 after inserton trigpart foreach row execute procedure trigger_nothing();
\d trigpart3 altertable trigpart detach partition trigpart3; droptrigger trg1 on trigpart3; -- fail due to "does not exist" altertable trigpart detach partition trigpart4; droptrigger trg1 on trigpart41; -- fail due to "does not exist" droptable trigpart4; altertable trigpart attach partition trigpart3 forvaluesfrom (2000) to (3000); altertable trigpart detach partition trigpart3; altertable trigpart attach partition trigpart3 forvaluesfrom (2000) to (3000); droptable trigpart3;
-- check display of unrelated triggers createtrigger samename after deleteon trigpart execute function trigger_nothing(); createtrigger samename after deleteon trigpart1 execute function trigger_nothing();
\d trigpart1
droptable trigpart; drop function trigger_nothing();
-- -- Verify that triggers are fired for partitioned tables -- createtable parted_stmt_trig (a int) partition by list (a); createtable parted_stmt_trig1 partition of parted_stmt_trig forvaluesin (1); createtable parted_stmt_trig2 partition of parted_stmt_trig forvaluesin (2);
createtable parted2_stmt_trig (a int) partition by list (a); createtable parted2_stmt_trig1 partition of parted2_stmt_trig forvaluesin (1); createtable parted2_stmt_trig2 partition of parted2_stmt_trig forvaluesin (2);
createorreplace function trigger_notice() returns triggeras $$
begin
raise notice 'trigger % on % % % for %', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL; if TG_LEVEL = 'ROW'then return NEW;
end if; returnnull;
end;
$$ language plpgsql;
with ins (a) as ( insertinto parted2_stmt_trig values (1), (2) returning a
) insertinto parted_stmt_trig select a from ins returning tableoid::regclass, a;
with upd as ( update parted2_stmt_trig set a = a
) update parted_stmt_trig set a = a;
deletefrom parted_stmt_trig;
-- insert via copy on the parent
copy parted_stmt_trig(a) from stdin; 1 2
\.
-- insert via copy on the first partition
copy parted_stmt_trig1(a) from stdin; 1
\.
-- Disabling a trigger in the parent table should disable children triggers too altertable parted_stmt_trig disable trigger trig_ins_after_parent; insertinto parted_stmt_trig values (1); altertable parted_stmt_trig enable trigger trig_ins_after_parent; insertinto parted_stmt_trig values (1);
droptable parted_stmt_trig, parted2_stmt_trig;
-- Verify that triggers fire in alphabetical order createtable parted_trig (a int) partition by range (a); createtable parted_trig_1 partition of parted_trig forvaluesfrom (0) to (1000)
partition by range (a); createtable parted_trig_1_1 partition of parted_trig_1 forvaluesfrom (0) to (100); createtable parted_trig_2 partition of parted_trig forvaluesfrom (1000) to (2000); createtrigger zzz after inserton parted_trig foreach row execute procedure trigger_notice(); createtrigger mmm after inserton parted_trig_1_1 foreach row execute procedure trigger_notice(); createtrigger aaa after inserton parted_trig_1 foreach row execute procedure trigger_notice(); createtrigger bbb after inserton parted_trig foreach row execute procedure trigger_notice(); createtrigger qqq after inserton parted_trig_1_1 foreach row execute procedure trigger_notice(); insertinto parted_trig values (50), (1500); droptable parted_trig;
-- Verify that the correct triggers fire for cross-partition updates createtable parted_trig (a int) partition by list (a); createtable parted_trig1 partition of parted_trig forvaluesin (1); createtable parted_trig2 partition of parted_trig forvaluesin (2); insertinto parted_trig values (1);
createorreplace function trigger_notice() returns triggeras $$
begin
raise notice 'trigger % on % % % for %', TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL; if TG_LEVEL = 'ROW'then if TG_OP = 'DELETE'then return OLD; else return NEW;
end if;
end if; returnnull;
end;
$$ language plpgsql; createtrigger parted_trig_before_stmt beforeinsertorupdateordeleteon parted_trig foreach statement execute procedure trigger_notice(); createtrigger parted_trig_before_row beforeinsertorupdateordeleteon parted_trig foreach row execute procedure trigger_notice(); createtrigger parted_trig_after_row after insertorupdateordeleteon parted_trig foreach row execute procedure trigger_notice(); createtrigger parted_trig_after_stmt after insertorupdateordeleteon parted_trig foreach statement execute procedure trigger_notice();
update parted_trig set a = 2where a = 1;
-- update action in merge should behave the same
merge into parted_trig using (select1) as ss ontrue when matched and a = 2thenupdateset a = 1;
droptable parted_trig;
-- Verify propagation of trigger arguments to partitions createtable parted_trig (a int) partition by list (a); createtable parted_trig1 partition of parted_trig forvaluesin (1); createorreplace function trigger_notice() returns triggeras $$ declare
arg1 text = TG_ARGV[0];
arg2 integer = TG_ARGV[1];
begin
raise notice 'trigger % on % % % for % args % %',
TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL, arg1, arg2; returnnull;
end;
$$ language plpgsql; createtrigger aaa after inserton parted_trig foreach row execute procedure trigger_notice('quirky', 1);
-- Verify propagation of trigger arguments to partitions attached after creating trigger createtable parted_trig2 partition of parted_trig forvaluesin (2); createtable parted_trig3 (like parted_trig); altertable parted_trig attach partition parted_trig3 forvaluesin (3); insertinto parted_trig values (1), (2), (3); droptable parted_trig;
-- test irregular partitions (i.e., different column definitions), -- including that the WHEN clause works create function bark(text) returns bool language plpgsql immutable as $$ begin raise notice '% <- woof!', $1; returntrue; end; $$; createorreplace function trigger_notice_ab() returns triggeras $$
begin
raise notice 'trigger % on % % % for %: (a,b)=(%,%)',
TG_NAME, TG_TABLE_NAME, TG_WHEN, TG_OP, TG_LEVEL,
NEW.a, NEW.b; if TG_LEVEL = 'ROW'then return NEW;
end if; returnnull;
end;
$$ language plpgsql; createtable parted_irreg_ancestor (fd text, b text, fd2 int, fd3 int, a int)
partition by range (b); altertable parted_irreg_ancestor dropcolumn fd, dropcolumn fd2, dropcolumn fd3; createtable parted_irreg (fd int, a int, fd2 int, b text)
partition by range (b); altertable parted_irreg dropcolumn fd, dropcolumn fd2; altertable parted_irreg_ancestor attach partition parted_irreg forvaluesfrom ('aaaa') to ('zzzz'); createtable parted1_irreg (b text, fd int, a int); altertable parted1_irreg dropcolumn fd; altertable parted_irreg attach partition parted1_irreg forvaluesfrom ('aaaa') to ('bbbb'); createtrigger parted_trig after inserton parted_irreg foreach row execute procedure trigger_notice_ab(); createtrigger parted_trig_odd after inserton parted_irreg foreach row when (bark(new.b) AND new.a % 2 = 1) execute procedure trigger_notice_ab(); -- we should hear barking for every insert, but parted_trig_odd only emits -- noise for odd values of a. parted_trig does it for all inserts. insertinto parted_irreg values (1, 'aardvark'), (2, 'aanimals'); insertinto parted1_irreg values ('aardwolf', 2); insertinto parted_irreg_ancestor values ('aasvogel', 3); droptable parted_irreg_ancestor;
-- Before triggers and partitions createtable parted (a int, b int, c text) partition by list (a); createtable parted_1 partition of parted forvaluesin (1)
partition by list (b); createtable parted_1_1 partition of parted_1 forvaluesin (1); create function parted_trigfunc() returns trigger language plpgsql as $$
begin
new.a = new.a + 1; return new;
end;
$$; insertinto parted values (1, 1, 'uno uno v1'); -- works createtrigger t beforeinsertorupdateordeleteon parted foreach row execute function parted_trigfunc(); insertinto parted values (1, 1, 'uno uno v2'); -- fail update parted set c = c || 'v3'; -- fail createorreplace function parted_trigfunc() returns trigger language plpgsql as $$
begin
new.b = new.b + 1; return new;
end;
$$; insertinto parted values (1, 1, 'uno uno v4'); -- fail update parted set c = c || 'v5'; -- fail createorreplace function parted_trigfunc() returns trigger language plpgsql as $$
begin
new.c = new.c || ' did '|| TG_OP; return new;
end;
$$; insertinto parted values (1, 1, 'uno uno'); -- works update parted set c = c || ' v6'; -- works select tableoid::regclass, * from parted;
-- update itself moves tuple to new partition; trigger still works
truncate table parted; createtable parted_2 partition of parted forvaluesin (2); insertinto parted values (1, 1, 'uno uno v5'); update parted set a = 2; select tableoid::regclass, * from parted;
-- both trigger and update change the partition createorreplace function parted_trigfunc2() returns trigger language plpgsql as $$
begin
new.a = new.a + 1; return new;
end;
$$; createtrigger t2 beforeupdateon parted foreach row execute function parted_trigfunc2();
truncate table parted; insertinto parted values (1, 1, 'uno uno v6'); createtable parted_3 partition of parted forvaluesin (3); update parted set a = a + 1; select tableoid::regclass, * from parted; -- there's no partition for a=0, but this update works anyway because -- the trigger causes the tuple to be routed to another partition update parted set a = 0; select tableoid::regclass, * from parted;
droptable parted; createtable parted (a int, b int, c text) partition by list ((a + b)); createorreplace function parted_trigfunc() returns trigger language plpgsql as $$
begin
new.a = new.a + new.b; return new;
end;
$$; createtable parted_1 partition of parted forvaluesin (1, 2); createtable parted_2 partition of parted forvaluesin (3, 4); createtrigger t beforeinsertorupdateon parted foreach row execute function parted_trigfunc(); insertinto parted values (0, 1, 'zero win'); insertinto parted values (1, 1, 'one fail'); insertinto parted values (1, 2, 'two fail'); select * from parted; droptable parted; drop function parted_trigfunc();
-- -- Constraint triggers and partitioned tables createtable parted_constr_ancestor (a int, b text)
partition by range (b); createtable parted_constr (a int, b text)
partition by range (b); altertable parted_constr_ancestor attach partition parted_constr forvaluesfrom ('aaaa') to ('zzzz'); createtable parted1_constr (a int, b text); altertable parted_constr attach partition parted1_constr forvaluesfrom ('aaaa') to ('bbbb'); createconstrainttrigger parted_trig after inserton parted_constr_ancestor
deferrable foreach row execute procedure trigger_notice_ab(); createconstrainttrigger parted_trig_two after inserton parted_constr
deferrable initially deferred foreach row when (bark(new.b) AND new.a % 2 = 1)
execute procedure trigger_notice_ab();
-- The immediate constraint is fired immediately; the WHEN clause of the -- deferred constraint is also called immediately. The deferred constraint -- is fired at commit time.
begin; insertinto parted_constr values (1, 'aardvark'); insertinto parted1_constr values (2, 'aardwolf'); insertinto parted_constr_ancestor values (3, 'aasvogel'); commit;
-- The WHEN clause is immediate, and both constraint triggers are fired at -- commit time.
begin; set constraints parted_trig deferred; insertinto parted_constr values (1, 'aardvark'); insertinto parted1_constr values (2, 'aardwolf'), (3, 'aasvogel'); commit; droptable parted_constr_ancestor; drop function bark(text);
-- Test that the WHEN clause is set properly to partitions createtable parted_trigger (a int, b text) partition by range (a); createtable parted_trigger_1 partition of parted_trigger forvaluesfrom (0) to (1000); createtable parted_trigger_2 (drp int, a int, b text); altertable parted_trigger_2 dropcolumn drp; altertable parted_trigger attach partition parted_trigger_2 forvaluesfrom (1000) to (2000); createtrigger parted_trigger after updateon parted_trigger foreach row when (new.a % 2 = 1and length(old.b) >= 2) execute procedure trigger_notice_ab(); createtable parted_trigger_3 (b text, a int) partition by range (length(b)); createtable parted_trigger_3_1 partition of parted_trigger_3 forvaluesfrom (1) to (3); createtable parted_trigger_3_2 partition of parted_trigger_3 forvaluesfrom (3) to (5); altertable parted_trigger attach partition parted_trigger_3 forvaluesfrom (2000) to (3000); insertinto parted_trigger values
(0, 'a'), (1, 'bbb'), (2, 'bcd'), (3, 'c'),
(1000, 'c'), (1001, 'ddd'), (1002, 'efg'), (1003, 'f'),
(2000, 'e'), (2001, 'fff'), (2002, 'ghi'), (2003, 'h'); update parted_trigger set a = a + 2; -- notice for odd 'a' values, long 'b' values droptable parted_trigger;
-- try a constraint trigger, also createtable parted_referenced (a int); createtable unparted_trigger (a int, b text); -- for comparison purposes createtable parted_trigger (a int, b text) partition by range (a); createtable parted_trigger_1 partition of parted_trigger forvaluesfrom (0) to (1000); createtable parted_trigger_2 (drp int, a int, b text); altertable parted_trigger_2 dropcolumn drp; altertable parted_trigger attach partition parted_trigger_2 forvaluesfrom (1000) to (2000); createconstrainttrigger parted_trigger after updateon parted_trigger from parted_referenced foreach row execute procedure trigger_notice_ab(); createconstrainttrigger parted_trigger after updateon unparted_trigger from parted_referenced foreach row execute procedure trigger_notice_ab(); createtable parted_trigger_3 (b text, a int) partition by range (length(b)); createtable parted_trigger_3_1 partition of parted_trigger_3 forvaluesfrom (1) to (3); createtable parted_trigger_3_2 partition of parted_trigger_3 forvaluesfrom (3) to (5); altertable parted_trigger attach partition parted_trigger_3 forvaluesfrom (2000) to (3000); select tgname, conname, t.tgrelid::regclass, t.tgconstrrelid::regclass,
c.conrelid::regclass, c.confrelid::regclass from pg_trigger t join pg_constraint c on (t.tgconstraint = c.oid) where tgname = 'parted_trigger' orderby t.tgrelid::regclass::text; droptable parted_referenced, parted_trigger, unparted_trigger;
-- verify that the "AFTER UPDATE OF columns" event is propagated correctly createtable parted_trigger (a int, b text) partition by range (a); createtable parted_trigger_1 partition of parted_trigger forvaluesfrom (0) to (1000); createtable parted_trigger_2 (drp int, a int, b text); altertable parted_trigger_2 dropcolumn drp; altertable parted_trigger attach partition parted_trigger_2 forvaluesfrom (1000) to (2000); createtrigger parted_trigger after update of b on parted_trigger foreach row execute procedure trigger_notice_ab(); createtable parted_trigger_3 (b text, a int) partition by range (length(b)); createtable parted_trigger_3_1 partition of parted_trigger_3 forvaluesfrom (1) to (4); createtable parted_trigger_3_2 partition of parted_trigger_3 forvaluesfrom (4) to (8); altertable parted_trigger attach partition parted_trigger_3 forvaluesfrom (2000) to (3000); insertinto parted_trigger values (0, 'a'), (1000, 'c'), (2000, 'e'), (2001, 'eeee'); update parted_trigger set a = a + 2; -- no notices here update parted_trigger set b = b || 'b'; -- all triggers should fire droptable parted_trigger;
drop function trigger_notice_ab();
-- Make sure we don't end up with unnecessary copies of triggers, when -- cloning them. createtable trg_clone (a int) partition by range (a); createtable trg_clone1 partition of trg_clone forvaluesfrom (0) to (1000); altertable trg_clone addconstraint uniq unique (a) deferrable; createtable trg_clone2 partition of trg_clone forvaluesfrom (1000) to (2000); createtable trg_clone3 partition of trg_clone forvaluesfrom (2000) to (3000)
partition by range (a); createtable trg_clone_3_3 partition of trg_clone3 forvaluesfrom (2000) to (2100); select tgrelid::regclass, count(*) from pg_trigger where tgrelid::regclass in ('trg_clone', 'trg_clone1', 'trg_clone2', 'trg_clone3', 'trg_clone_3_3') groupby tgrelid::regclass orderby tgrelid::regclass; droptable trg_clone;
-- Test the interaction between ALTER TABLE .. DISABLE TRIGGER and -- both kinds of inheritance. Historically, legacy inheritance has -- not recursed to children, so that behavior is preserved. createtable parent (a int); createtable child1 () inherits (parent); create function trig_nothing() returns trigger language plpgsql as $$ begin returnnull; end $$; createtrigger tg after inserton parent foreach row execute function trig_nothing(); createtrigger tg after inserton child1 foreach row execute function trig_nothing(); altertable parent disable trigger tg; select tgrelid::regclass, tgname, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text; altertable only parent enable always trigger tg; select tgrelid::regclass, tgname, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text; droptable parent, child1;
createtable parent (a int) partition by list (a); createtable child1 partition of parent forvaluesin (1); createtrigger tg after inserton parent foreach row execute procedure trig_nothing(); createtrigger tg_stmt after inserton parent for statement execute procedure trig_nothing(); select tgrelid::regclass, tgname, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text, tgname; altertable only parent enable always trigger tg; -- no recursion because ONLY altertable parent enable always trigger tg_stmt; -- no recursion because statement trigger select tgrelid::regclass, tgname, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text, tgname; -- The following is a no-op for the parent trigger but not so -- for the child trigger, so recursion should be applied. altertable parent enable always trigger tg; select tgrelid::regclass, tgname, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text, tgname; -- This variant malfunctioned in some releases. altertable parent disable trigger user; select tgrelid::regclass, tgname, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text, tgname; droptable parent, child1;
-- Check processing of foreign key triggers createtable parent (a intprimarykey, f intreferences parent)
partition by list (a); createtable child1 partition of parent forvaluesin (1); select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
tgfoid::regproc, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text, tgfoid; altertable parent disable triggerall; select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
tgfoid::regproc, tgenabled from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) orderby tgrelid::regclass::text, tgfoid; droptable parent, child1;
-- Verify that firing state propagates correctly on creation, too CREATETABLE trgfire (i int) PARTITION BY RANGE (i); CREATETABLE trgfire1 PARTITION OF trgfire FORVALUESFROM (1) TO (10); CREATEORREPLACE FUNCTION tgf() RETURNS trigger LANGUAGE plpgsql AS $$ begin raise exception 'except'; end $$; CREATETRIGGER tg AFTER INSERTON trgfire FOREACH ROW EXECUTE FUNCTION tgf(); INSERTINTO trgfire VALUES (1); ALTERTABLE trgfire DISABLE TRIGGER tg; INSERTINTO trgfire VALUES (1); CREATETABLE trgfire2 PARTITION OF trgfire FORVALUESFROM (10) TO (20); INSERTINTO trgfire VALUES (11); CREATETABLE trgfire3 (LIKE trgfire); ALTERTABLE trgfire ATTACH PARTITION trgfire3 FORVALUESFROM (20) TO (30); INSERTINTO trgfire VALUES (21); CREATETABLE trgfire4 PARTITION OF trgfire FORVALUESFROM (30) TO (40) PARTITION BY LIST (i); CREATETABLE trgfire4_30 PARTITION OF trgfire4 FORVALUESIN (30); INSERTINTO trgfire VALUES (30); CREATETABLE trgfire5 (LIKE trgfire) PARTITION BY LIST (i); CREATETABLE trgfire5_40 PARTITION OF trgfire5 FORVALUESIN (40); ALTERTABLE trgfire ATTACH PARTITION trgfire5 FORVALUESFROM (40) TO (50); INSERTINTO trgfire VALUES (40); SELECT tgrelid::regclass, tgenabled FROM pg_trigger WHERE tgrelid::regclass IN (SELECT oid from pg_class where relname LIKE'trgfire%') ORDERBY tgrelid::regclass::text; ALTERTABLE trgfire ENABLE TRIGGER tg; INSERTINTO trgfire VALUES (1); INSERTINTO trgfire VALUES (11); INSERTINTO trgfire VALUES (21); INSERTINTO trgfire VALUES (30); INSERTINTO trgfire VALUES (40); DROPTABLE trgfire; DROP FUNCTION tgf();
-- -- Test the interaction between transition tables and both kinds of -- inheritance. We'll dump the contents of the transition tables in a -- format that shows the attribute order, so that we can distinguish -- tuple formats (though not dropped attributes). --
createorreplace function dump_insert() returns trigger language plpgsql as
$$
begin
raise notice 'trigger = %, new table = %',
TG_NAME,
(select string_agg(new_table::text, ', 'orderby a) from new_table); returnnull;
end;
$$;
createorreplace function dump_update() returns trigger language plpgsql as
$$
begin
raise notice 'trigger = %, old table = %, new table = %',
TG_NAME,
(select string_agg(old_table::text, ', 'orderby a) from old_table),
(select string_agg(new_table::text, ', 'orderby a) from new_table); returnnull;
end;
$$;
createorreplace function dump_delete() returns trigger language plpgsql as
$$
begin
raise notice 'trigger = %, old table = %',
TG_NAME,
(select string_agg(old_table::text, ', 'orderby a) from old_table); returnnull;
end;
$$;
-- -- Verify behavior of statement triggers on partition hierarchy with -- transition tables. Tuples should appear to each trigger in the -- format of the relation the trigger is attached to. --
-- set up a partition hierarchy with some different TupleDescriptors createtable parent (a text, b int) partition by list (a);
-- a child matching parent createtable child1 partition of parent forvaluesin ('AAA');
-- a child with a dropped column createtable child2 (x int, a text, b int); altertable child2 dropcolumn x; altertable parent attach partition child2 forvaluesin ('BBB');
-- a child with a different column order createtable child3 (b int, a text); altertable parent attach partition child3 forvaluesin ('CCC');
createtrigger parent_insert_trig
after inserton parent referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger parent_update_trig
after updateon parent referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger parent_delete_trig
after deleteon parent referencing old tableas old_table foreach statement execute procedure dump_delete();
createtrigger child1_insert_trig
after inserton child1 referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger child1_update_trig
after updateon child1 referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger child1_delete_trig
after deleteon child1 referencing old tableas old_table foreach statement execute procedure dump_delete();
createtrigger child2_insert_trig
after inserton child2 referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger child2_update_trig
after updateon child2 referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger child2_delete_trig
after deleteon child2 referencing old tableas old_table foreach statement execute procedure dump_delete();
createtrigger child3_insert_trig
after inserton child3 referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger child3_update_trig
after updateon child3 referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger child3_delete_trig
after deleteon child3 referencing old tableas old_table foreach statement execute procedure dump_delete();
SELECT trigger_name, event_manipulation, event_object_schema, event_object_table,
action_order, action_condition, action_orientation, action_timing,
action_reference_old_table, action_reference_new_table FROM information_schema.triggers WHERE event_object_table IN ('parent', 'child1', 'child2', 'child3') ORDERBY trigger_name COLLATE"C", 2;
-- insert directly into children sees respective child-format tuples insertinto child1 values ('AAA', 42); insertinto child2 values ('BBB', 42); insertinto child3 values (42, 'CCC');
-- update via parent sees parent-format tuples update parent set b = b + 1;
-- delete via parent sees parent-format tuples deletefrom parent;
-- delete from children sees respective child-format tuples deletefrom child1; deletefrom child2; deletefrom child3;
-- copy into parent sees parent-format tuples
copy parent (a, b) from stdin;
AAA 42
BBB 42
CCC 42
\.
-- check detach/reattach behavior; statement triggers with transition tables -- should not prevent a table from becoming a partition again altertable parent detach partition child1; altertable parent attach partition child1 forvaluesin ('AAA');
-- DML affecting parent sees tuples collected from children even if -- there is no transition table trigger on the children droptrigger child1_insert_trig on child1; droptrigger child1_update_trig on child1; droptrigger child1_delete_trig on child1; droptrigger child2_insert_trig on child2; droptrigger child2_update_trig on child2; droptrigger child2_delete_trig on child2; droptrigger child3_insert_trig on child3; droptrigger child3_update_trig on child3; droptrigger child3_delete_trig on child3; deletefrom parent;
-- copy into parent sees tuples collected from children even if there -- is no transition-table trigger on the children
copy parent (a, b) from stdin;
AAA 42
BBB 42
CCC 42
\.
-- insert into parent with a before trigger on a child tuple before -- insertion, and we capture the newly modified row in parent format createorreplace function intercept_insert() returns trigger language plpgsql as
$$
begin
new.b = new.b + 1000; return new;
end;
$$;
-- copy, parent trigger sees post-modification parent-format tuple
copy parent (a, b) from stdin;
AAA 42
BBB 42
CCC 234
\.
droptable child1, child2, child3, parent; drop function intercept_insert();
-- -- Verify prohibition of row triggers with transition triggers on -- partitions -- createtable parent (a text, b int) partition by list (a); createtable child partition of parent forvaluesin ('AAA');
-- adding row trigger with transition table fails createtrigger child_row_trig
after inserton child referencing new tableas new_table foreach row execute procedure dump_insert();
-- detaching it first works altertable parent detach partition child;
createtrigger child_row_trig
after inserton child referencing new tableas new_table foreach row execute procedure dump_insert();
-- but now we're not allowed to reattach it altertable parent attach partition child forvaluesin ('AAA');
-- drop the trigger, and now we're allowed to attach it again droptrigger child_row_trig on child; altertable parent attach partition child forvaluesin ('AAA');
droptable child, parent;
-- -- Verify access of transition tables with UPDATE triggers and tuples -- moved across partitions. -- createorreplace function dump_update_new() returns trigger language plpgsql as
$$
begin
raise notice 'trigger = %, new table = %', TG_NAME,
(select string_agg(new_table::text, ', 'orderby a) from new_table); returnnull;
end;
$$; createorreplace function dump_update_old() returns trigger language plpgsql as
$$
begin
raise notice 'trigger = %, old table = %', TG_NAME,
(select string_agg(old_table::text, ', 'orderby a) from old_table); returnnull;
end;
$$; createtable trans_tab_parent (a text) partition by list (a); createtable trans_tab_child1 partition of trans_tab_parent forvaluesin ('AAA1', 'AAA2'); createtable trans_tab_child2 partition of trans_tab_parent forvaluesin ('BBB1', 'BBB2'); createtrigger trans_tab_parent_update_trig
after updateon trans_tab_parent referencing old tableas old_table foreach statement execute procedure dump_update_old(); createtrigger trans_tab_parent_insert_trig
after inserton trans_tab_parent referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger trans_tab_parent_delete_trig
after deleteon trans_tab_parent referencing old tableas old_table foreach statement execute procedure dump_delete(); insertinto trans_tab_parent values ('AAA1'), ('BBB1'); -- should not trigger access to new table when moving across partitions. update trans_tab_parent set a = 'BBB2'where a = 'AAA1'; droptrigger trans_tab_parent_update_trig on trans_tab_parent; createtrigger trans_tab_parent_update_trig
after updateon trans_tab_parent referencing new tableas new_table foreach statement execute procedure dump_update_new(); -- should not trigger access to old table when moving across partitions. update trans_tab_parent set a = 'AAA2'where a = 'BBB1'; deletefrom trans_tab_parent; -- clean up droptable trans_tab_parent, trans_tab_child1, trans_tab_child2; drop function dump_update_new, dump_update_old;
-- -- Verify behavior of statement triggers on (non-partition) -- inheritance hierarchy with transition tables; similar to the -- partition case, except there is no rerouting on insertion and child -- tables can have extra columns --
-- set up inheritance hierarchy with different TupleDescriptors createtable parent (a text, b int);
-- a child matching parent createtable child1 () inherits (parent);
-- a child with a different column order createtable child2 (b int, a text); altertable child2 inherit parent;
-- a child with an extra column createtable child3 (c text) inherits (parent);
createtrigger parent_insert_trig
after inserton parent referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger parent_update_trig
after updateon parent referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger parent_delete_trig
after deleteon parent referencing old tableas old_table foreach statement execute procedure dump_delete();
createtrigger child1_insert_trig
after inserton child1 referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger child1_update_trig
after updateon child1 referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger child1_delete_trig
after deleteon child1 referencing old tableas old_table foreach statement execute procedure dump_delete();
createtrigger child2_insert_trig
after inserton child2 referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger child2_update_trig
after updateon child2 referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger child2_delete_trig
after deleteon child2 referencing old tableas old_table foreach statement execute procedure dump_delete();
createtrigger child3_insert_trig
after inserton child3 referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger child3_update_trig
after updateon child3 referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger child3_delete_trig
after deleteon child3 referencing old tableas old_table foreach statement execute procedure dump_delete();
-- insert directly into children sees respective child-format tuples insertinto child1 values ('AAA', 42); insertinto child2 values (42, 'BBB'); insertinto child3 values ('CCC', 42, 'foo');
-- update via parent sees parent-format tuples update parent set b = b + 1;
-- delete via parent sees parent-format tuples deletefrom parent;
-- reinsert values into children for next test... insertinto child1 values ('AAA', 42); insertinto child2 values (42, 'BBB'); insertinto child3 values ('CCC', 42, 'foo');
-- delete from children sees respective child-format tuples deletefrom child1; deletefrom child2; deletefrom child3;
-- copy into parent sees parent-format tuples (no rerouting, so these -- are really inserted into the parent)
copy parent (a, b) from stdin;
AAA 42
BBB 42
CCC 42
\.
-- same behavior for copy if there is an index (interesting because rows are -- captured by a different code path in copyfrom.c if there are indexes) createindexon parent(b);
copy parent (a, b) from stdin;
DDD 42
\.
-- check disinherit/reinherit behavior; statement triggers with transition -- tables should not prevent a table from becoming an inheritance child again altertable child1 no inherit parent; altertable child1 inherit parent;
-- DML affecting parent sees tuples collected from children even if -- there is no transition table trigger on the children droptrigger child1_insert_trig on child1; droptrigger child1_update_trig on child1; droptrigger child1_delete_trig on child1; droptrigger child2_insert_trig on child2; droptrigger child2_update_trig on child2; droptrigger child2_delete_trig on child2; droptrigger child3_insert_trig on child3; droptrigger child3_update_trig on child3; droptrigger child3_delete_trig on child3; deletefrom parent;
droptable child1, child2, child3, parent;
-- -- Verify prohibition of row triggers with transition triggers on -- inheritance children -- createtable parent (a text, b int); createtable child () inherits (parent);
-- adding row trigger with transition table fails createtrigger child_row_trig
after inserton child referencing new tableas new_table foreach row execute procedure dump_insert();
-- disinheriting it first works altertable child no inherit parent;
createtrigger child_row_trig
after inserton child referencing new tableas new_table foreach row execute procedure dump_insert();
-- but now we're not allowed to make it inherit anymore altertable child inherit parent;
-- drop the trigger, and now we're allowed to make it inherit again droptrigger child_row_trig on child; altertable child inherit parent;
droptable child, parent;
-- -- Verify behavior of queries with wCTEs, where multiple transition -- tuplestores can be active at the same time because there are -- multiple DML statements that might fire triggers with transition -- tables -- createtable table1 (a int); createtable table2 (a text); createtrigger table1_trig
after inserton table1 referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger table2_trig
after inserton table2 referencing new tableas new_table foreach statement execute procedure dump_insert();
with wcte as (insertinto table1 values (42)) insertinto table2 values ('hello world');
with wcte as (insertinto table1 values (43)) insertinto table1 values (44);
with wcte as (insertinto table1 values (45))
merge into table1 using (values (46)) as v(a) on table1.a = v.a whennot matched theninsertvalues (v.a);
select * from table1; select * from table2;
droptable table1; droptable table2;
-- -- Verify behavior of INSERT ... ON CONFLICT DO UPDATE ... with -- transition tables. --
createtable my_table (a intprimarykey, b text); createtrigger my_table_insert_trig
after inserton my_table referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger my_table_update_trig
after updateon my_table referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update();
-- inserts only insertinto my_table values (1, 'AAA'), (2, 'BBB') on conflict (a) do updateset b = my_table.b || ':' || excluded.b;
-- mixture of inserts and updates insertinto my_table values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') on conflict (a) do updateset b = my_table.b || ':' || excluded.b;
-- updates only insertinto my_table values (3, 'CCC'), (4, 'DDD') on conflict (a) do updateset b = my_table.b || ':' || excluded.b;
-- -- now using a partitioned table --
createtable iocdu_tt_parted (a intprimarykey, b text) partition by list (a); createtable iocdu_tt_parted1 partition of iocdu_tt_parted forvaluesin (1); createtable iocdu_tt_parted2 partition of iocdu_tt_parted forvaluesin (2); createtable iocdu_tt_parted3 partition of iocdu_tt_parted forvaluesin (3); createtable iocdu_tt_parted4 partition of iocdu_tt_parted forvaluesin (4); createtrigger iocdu_tt_parted_insert_trig
after inserton iocdu_tt_parted referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger iocdu_tt_parted_update_trig
after updateon iocdu_tt_parted referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update();
-- inserts only insertinto iocdu_tt_parted values (1, 'AAA'), (2, 'BBB') on conflict (a) do updateset b = iocdu_tt_parted.b || ':' || excluded.b;
-- mixture of inserts and updates insertinto iocdu_tt_parted values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') on conflict (a) do updateset b = iocdu_tt_parted.b || ':' || excluded.b;
-- updates only insertinto iocdu_tt_parted values (3, 'CCC'), (4, 'DDD') on conflict (a) do updateset b = iocdu_tt_parted.b || ':' || excluded.b;
droptable iocdu_tt_parted;
-- -- Verify that you can't create a trigger with transition tables for -- more than one event. --
createtrigger my_table_multievent_trig
after insertorupdateon my_table referencing new tableas new_table foreach statement execute procedure dump_insert();
-- -- Verify that you can't create a trigger with transition tables with -- a column list. --
createtrigger my_table_col_update_trig
after update of b on my_table referencing new tableas new_table foreach statement execute procedure dump_insert();
droptable my_table;
-- -- Verify that transition tables can't be used in, eg, a view. --
createtable my_table (a int); create function make_bogus_matview() returns triggeras
$$ begin create materialized view transition_test_mv asselect * from new_table; return new;
end $$
language plpgsql; createtrigger make_bogus_matview
after inserton my_table
referencing new tableas new_table foreach statement execute function make_bogus_matview(); insertinto my_table values (42); -- error droptable my_table; drop function make_bogus_matview();
-- -- Test firing of triggers with transition tables by foreign key cascades --
createtable refd_table (a intprimarykey, b text); createtable trig_table (a int, b text, foreignkey (a) references refd_table onupdatecascadeondeletecascade
);
createtrigger trig_table_before_trig beforeinsertorupdateordeleteon trig_table foreach statement execute procedure trigger_func('trig_table'); createtrigger trig_table_insert_trig
after inserton trig_table referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger trig_table_update_trig
after updateon trig_table referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger trig_table_delete_trig
after deleteon trig_table referencing old tableas old_table foreach statement execute procedure dump_delete();
-- -- test transition tables with MERGE -- createtable merge_target_table (a intprimarykey, b text); createtrigger merge_target_table_insert_trig
after inserton merge_target_table referencing new tableas new_table foreach statement execute procedure dump_insert(); createtrigger merge_target_table_update_trig
after updateon merge_target_table referencing old tableas old_table new tableas new_table foreach statement execute procedure dump_update(); createtrigger merge_target_table_delete_trig
after deleteon merge_target_table referencing old tableas old_table foreach statement execute procedure dump_delete();
createtable merge_source_table (a int, b text); insertinto merge_source_table values (1, 'initial1'), (2, 'initial2'),
(3, 'initial3'), (4, 'initial4');
merge into merge_target_table t using merge_source_table s on t.a = s.a whennot matched then insertvalues (a, b);
merge into merge_target_table t using merge_source_table s on t.a = s.a when matched and s.a <= 2then updateset b = t.b || ' updated by merge' when matched and s.a > 2then delete whennot matched then insertvalues (a, b);
merge into merge_target_table t using merge_source_table s on t.a = s.a when matched and s.a <= 2then updateset b = t.b || ' updated again by merge' when matched and s.a > 2then delete whennot matched then insertvalues (a, b);
droptable merge_source_table, merge_target_table;
-- cleanup drop function dump_insert(); drop function dump_update(); drop function dump_delete();
-- -- Tests for CREATE OR REPLACE TRIGGER -- createtable my_table (id integer);
create function funcA() returns triggeras $$
begin
raise notice 'hello from funcA'; returnnull;
end; $$ language plpgsql;
create function funcB() returns triggeras $$
begin
raise notice 'hello from funcB'; returnnull;
end; $$ language plpgsql;
createtrigger my_trig
after inserton my_table foreach row execute procedure funcA();
insertinto my_table values (2); -- this insert should become a no-op
table my_table;
droptable my_table;
-- test CREATE OR REPLACE TRIGGER on partition table createtable parted_trig (a int) partition by range (a); createtable parted_trig_1 partition of parted_trig forvaluesfrom (0) to (1000) partition by range (a); createtable parted_trig_1_1 partition of parted_trig_1 forvaluesfrom (0) to (100); createtable parted_trig_2 partition of parted_trig forvaluesfrom (1000) to (2000); createtable default_parted_trig partition of parted_trig default;
-- test that trigger can be replaced by another one -- at the same level of partition table createorreplacetrigger my_trig
after inserton parted_trig foreach row execute procedure funcA(); insertinto parted_trig (a) values (50); createorreplacetrigger my_trig
after inserton parted_trig foreach row execute procedure funcB(); insertinto parted_trig (a) values (50);
-- test that child trigger cannot be replaced directly createorreplacetrigger my_trig
after inserton parted_trig foreach row execute procedure funcA(); insertinto parted_trig (a) values (50); createorreplacetrigger my_trig
after inserton parted_trig_1 foreach row execute procedure funcB(); -- should fail insertinto parted_trig (a) values (50); droptrigger my_trig on parted_trig; insertinto parted_trig (a) values (50);
-- test that user trigger can be overwritten by one defined at upper level createtrigger my_trig
after inserton parted_trig_1 foreach row execute procedure funcA(); insertinto parted_trig (a) values (50); createtrigger my_trig
after inserton parted_trig foreach row execute procedure funcB(); -- should fail insertinto parted_trig (a) values (50); createorreplacetrigger my_trig
after inserton parted_trig foreach row execute procedure funcB(); insertinto parted_trig (a) values (50);
-- cleanup droptable parted_trig; drop function funcA(); drop function funcB();
-- Leave around some objects for other tests createtable trigger_parted (a intprimarykey) partition by list (a); create function trigger_parted_trigfunc() returns trigger language plpgsql as
$$ begin end; $$; createtrigger aft_row after insertorupdateon trigger_parted foreach row execute function trigger_parted_trigfunc(); createtable trigger_parted_p1 partition of trigger_parted forvaluesin (1)
partition by list (a); createtable trigger_parted_p1_1 partition of trigger_parted_p1 forvaluesin (1); createtable trigger_parted_p2 partition of trigger_parted forvaluesin (2)
partition by list (a); createtable trigger_parted_p2_2 partition of trigger_parted_p2 forvaluesin (2); altertable only trigger_parted_p2 disable trigger aft_row; altertable trigger_parted_p2_2 enable always trigger aft_row;
create function convslot_trig1()
returns trigger
language plpgsql AS $$
begin
raise notice 'trigger = %, old_table = %',
TG_NAME,
(select string_agg(old_table::text, ', 'orderby col1) from old_table); returnnull;
end; $$;
create function convslot_trig2()
returns trigger
language plpgsql AS $$
begin
raise notice 'trigger = %, new table = %',
TG_NAME,
(select string_agg(new_table::text, ', 'orderby col1) from new_table); returnnull;
end; $$;
createtrigger but_trigger after updateon convslot_test_child
referencing new tableas new_table foreach statement execute function convslot_trig2();
update convslot_test_parent set col1 = col1 || '1';
create function convslot_trig3()
returns trigger
language plpgsql AS $$
begin
raise notice 'trigger = %, old_table = %, new table = %',
TG_NAME,
(select string_agg(old_table::text, ', 'orderby col1) from old_table),
(select string_agg(new_table::text, ', 'orderby col1) from new_table); returnnull;
end; $$;
createtrigger but_trigger2 after updateon convslot_test_child
referencing old tableas old_table new tableas new_table foreach statement execute function convslot_trig3(); update convslot_test_parent set col1 = col1 || '1';
createtrigger bdt_trigger after deleteon convslot_test_child
referencing old tableas old_table foreach statement execute function convslot_trig1(); deletefrom convslot_test_parent;
droptable convslot_test_child, convslot_test_parent; drop function convslot_trig1(); drop function convslot_trig2(); drop function convslot_trig3();
-- Bug #17607: variant of above in which trigger function raises an error; -- we don't see any ill effects unless trigger tuple requires mapping
createtable convslot_test_parent (id intprimarykey, val int)
partition by range (id);
createtable convslot_test_part (val int, id intnotnull);
altertable convslot_test_parent
attach partition convslot_test_part forvaluesfrom (1) to (1000);
create function convslot_trig4() returns triggeras
$$begin raise exception 'BOOM!'; end$$ language plpgsql;
createtrigger convslot_test_parent_update
after updateon convslot_test_parent
referencing old tableas old_rows new tableas new_rows foreach statement execute procedure convslot_trig4();
begin;
savepoint svp; update convslot_test_parent set val = 3; -- error expected
rollback to savepoint svp;
rollback;
droptable convslot_test_parent; drop function convslot_trig4();
-- Test trigger renaming on partitioned tables createtable grandparent (id int, primarykey (id)) partition by range (id); createtable middle partition of grandparent forvaluesfrom (1) to (10)
partition by range (id); createtable chi partition of middle forvaluesfrom (1) to (5); createtable cho partition of middle forvaluesfrom (6) to (10); create function f () returns triggeras
$$ begin return new; end; $$
language plpgsql; createtrigger a after inserton grandparent foreach row execute procedure f();
altertrigger a on grandparent renameto b; select tgrelid::regclass, tgname,
(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) orderby tgname, tgrelid::regclass::text COLLATE"C"; altertrigger a on only grandparent renameto b; -- ONLY not supported altertrigger b on middle renameto c; -- can't rename trigger on partition createtrigger c after inserton middle foreach row execute procedure f(); altertrigger b on grandparent renameto c;
-- Rename cascading does not affect statement triggers createtrigger p after inserton grandparent foreach statement execute function f(); createtrigger p after inserton middle foreach statement execute function f(); altertrigger p on grandparent renameto q; select tgrelid::regclass, tgname,
(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) orderby tgname, tgrelid::regclass::text COLLATE"C";
droptable grandparent;
-- Trigger renaming does not recurse on legacy inheritance createtable parent (a int); createtable child () inherits (parent); createtrigger parenttrig after inserton parent foreach row execute procedure f(); createtrigger parenttrig after inserton child foreach row execute procedure f(); altertrigger parenttrig on parent renameto anothertrig;
\d+ child
droptable parent, child; drop function f();
-- Test who runs deferred trigger functions
-- setup create role regress_caller; create role regress_fn_owner; create function whoami() returns trigger language plpgsql as $$
begin
raise notice 'I am %', current_user; returnnull;
end;
$$; alter function whoami() owner to regress_fn_owner;
createtable defer_trig (id integer); grantinserton defer_trig to public; createconstrainttrigger whoami after inserton defer_trig
deferrable initially deferred foreach row
execute function whoami();
-- deferred triggers must run as the user that queued the trigger
begin; set role regress_caller; insertinto defer_trig values (1);
reset role; set role regress_fn_owner; insertinto defer_trig values (2);
reset role; commit;
-- security definer functions override the user who queued the trigger alter function whoami() security definer;
begin; set role regress_caller; insertinto defer_trig values (3);
reset role; commit; alter function whoami() security invoker;
-- make sure the current user is restored after error createorreplace function whoami() returns trigger language plpgsql as $$
begin
raise notice 'I am %', current_user;
perform 1 / 0; returnnull;
end;
$$;
begin; set role regress_caller; insertinto defer_trig values (4);
reset role; commit; -- error expected selectcurrent_user = session_user;
-- clean up droptable defer_trig; drop function whoami(); drop role regress_fn_owner; drop role regress_caller;
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.80Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.