diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 240fbafade4..6fd8faf8c82 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -572,12 +572,12 @@ WITH ( MODULUS numeric_literal, REM These forms configure the firing of trigger(s) belonging to the table. A disabled trigger is still known to the system, but is not executed - when its triggering event occurs. For a deferred trigger, the enable + when its triggering event occurs. (For a deferred trigger, the enable status is checked when the event occurs, not when the trigger function - is actually executed. One can disable or enable a single + is actually executed.) One can disable or enable a single trigger specified by name, or all triggers on the table, or only user triggers (this option excludes internally generated constraint - triggers such as those that are used to implement foreign key + triggers, such as those that are used to implement foreign key constraints or deferrable uniqueness and exclusion constraints). Disabling or enabling internally generated constraint triggers requires superuser privileges; it should be done with caution since @@ -599,7 +599,7 @@ WITH ( MODULUS numeric_literal, REM The effect of this mechanism is that in the default configuration, triggers do not fire on replicas. This is useful because if a trigger is used on the origin to propagate data between tables, then the - replication system will also replicate the propagated data, and the + replication system will also replicate the propagated data; so the trigger should not fire a second time on the replica, because that would lead to duplication. However, if a trigger is used for another purpose such as creating external alerts, then it might be appropriate to set it @@ -607,6 +607,12 @@ WITH ( MODULUS numeric_literal, REM replicas. + + When this command is applied to a partitioned table, the states of + corresponding clone triggers in the partitions are updated too, + unless ONLY is specified. + + This command acquires a SHARE ROW EXCLUSIVE lock. @@ -1234,7 +1240,7 @@ WITH ( MODULUS numeric_literal, REM Disable or enable all triggers belonging to the table. (This requires superuser privilege if any of the triggers are - internally generated constraint triggers such as those that are used + internally generated constraint triggers, such as those that are used to implement foreign key constraints or deferrable uniqueness and exclusion constraints.) @@ -1246,7 +1252,7 @@ WITH ( MODULUS numeric_literal, REM Disable or enable all triggers belonging to the table except for - internally generated constraint triggers such as those that are used + internally generated constraint triggers, such as those that are used to implement foreign key constraints or deferrable uniqueness and exclusion constraints. @@ -1499,9 +1505,12 @@ WITH ( MODULUS numeric_literal, REM The actions for identity columns (ADD GENERATED, SET etc., DROP IDENTITY), as well as the actions - TRIGGER, CLUSTER, OWNER, + CLUSTER, OWNER, and TABLESPACE never recurse to descendant tables; that is, they always act as though ONLY were specified. + Actions affecting trigger states recurse to partitions of partitioned + tables (unless ONLY is specified), but never to + traditional-inheritance descendants. Adding a constraint recurses only for CHECK constraints that are not marked NO INHERIT. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index b59cc96719e..ce32c79ae06 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -14768,8 +14768,9 @@ ATExecEnableDisableTrigger(Relation rel, const char *trigname, char fires_when, bool skip_system, bool recurse, LOCKMODE lockmode) { - EnableDisableTriggerNew(rel, trigname, fires_when, skip_system, recurse, - lockmode); + EnableDisableTriggerNew2(rel, trigname, InvalidOid, + fires_when, skip_system, recurse, + lockmode); } /* diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 36f10824077..d9d9201ac38 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -1754,7 +1754,8 @@ renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid, * to change 'tgenabled' field for the specified trigger(s) * * rel: relation to process (caller must hold suitable lock on it) - * tgname: trigger to process, or NULL to scan all triggers + * tgname: name of trigger to process, or NULL to scan all triggers + * tgparent: if not zero, process only triggers with this tgparentid * fires_when: new value for tgenabled field. In addition to generic * enablement/disablement, this also defines when the trigger * should be fired in session replication roles. @@ -1766,9 +1767,9 @@ renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid, * system triggers */ void -EnableDisableTriggerNew(Relation rel, const char *tgname, - char fires_when, bool skip_system, bool recurse, - LOCKMODE lockmode) +EnableDisableTriggerNew2(Relation rel, const char *tgname, Oid tgparent, + char fires_when, bool skip_system, bool recurse, + LOCKMODE lockmode) { Relation tgrel; int nkeys; @@ -1805,6 +1806,9 @@ EnableDisableTriggerNew(Relation rel, const char *tgname, { Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple); + if (OidIsValid(tgparent) && tgparent != oldtrig->tgparentid) + continue; + if (oldtrig->tgisinternal) { /* system trigger ... ok to process? */ @@ -1855,9 +1859,10 @@ EnableDisableTriggerNew(Relation rel, const char *tgname, Relation part; part = relation_open(partdesc->oids[i], lockmode); - EnableDisableTriggerNew(part, NameStr(oldtrig->tgname), - fires_when, skip_system, recurse, - lockmode); + /* Match on child triggers' tgparentid, not their name */ + EnableDisableTriggerNew2(part, NULL, oldtrig->oid, + fires_when, skip_system, recurse, + lockmode); table_close(part, NoLock); /* keep lock till commit */ } } @@ -1886,16 +1891,27 @@ EnableDisableTriggerNew(Relation rel, const char *tgname, } /* - * ABI-compatible wrapper for the above. To keep as close possible to the old - * behavior, this never recurses. Do not call this function in new code. + * ABI-compatible wrappers to emulate old versions of the above function. + * Do not call these versions in new code. */ +void +EnableDisableTriggerNew(Relation rel, const char *tgname, + char fires_when, bool skip_system, bool recurse, + LOCKMODE lockmode) +{ + EnableDisableTriggerNew2(rel, tgname, InvalidOid, + fires_when, skip_system, + recurse, lockmode); +} + void EnableDisableTrigger(Relation rel, const char *tgname, char fires_when, bool skip_system, LOCKMODE lockmode) { - EnableDisableTriggerNew(rel, tgname, fires_when, skip_system, - true, lockmode); + EnableDisableTriggerNew2(rel, tgname, InvalidOid, + fires_when, skip_system, + true, lockmode); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0377438861c..0d7558ee6ef 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -170,6 +170,9 @@ extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok); extern ObjectAddress renametrig(RenameStmt *stmt); +extern void EnableDisableTriggerNew2(Relation rel, const char *tgname, Oid tgparent, + char fires_when, bool skip_system, bool recurse, + LOCKMODE lockmode); extern void EnableDisableTriggerNew(Relation rel, const char *tgname, char fires_when, bool skip_system, bool recurse, LOCKMODE lockmode); diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 6d80ab1a6d8..66d473ab031 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -2718,6 +2718,40 @@ select tgrelid::regclass, tgname, tgenabled from pg_trigger parent | tg_stmt | A (3 rows) +drop table parent, child1; +-- Check processing of foreign key triggers +create table parent (a int primary key, f int references parent) + partition by list (a); +create table child1 partition of parent for values in (1); +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; + tgrelid | tgname | tgfoid | tgenabled +---------+-------------------------+------------------------+----------- + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O +(6 rows) + +alter table parent disable trigger all; +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; + tgrelid | tgname | tgfoid | tgenabled +---------+-------------------------+------------------------+----------- + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D + child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D + parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | D + parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | D +(6 rows) + drop table parent, child1; -- Verify that firing state propagates correctly on creation, too CREATE TABLE trgfire (i int) PARTITION BY RANGE (i); diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 4d8504fb246..4222a8500b4 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -1883,6 +1883,21 @@ select tgrelid::regclass, tgname, tgenabled from pg_trigger order by tgrelid::regclass::text, tgname; drop table parent, child1; +-- Check processing of foreign key triggers +create table parent (a int primary key, f int references parent) + partition by list (a); +create table child1 partition of parent for values in (1); +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; +alter table parent disable trigger all; +select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, + tgfoid::regproc, tgenabled + from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) + order by tgrelid::regclass::text, tgfoid; +drop table parent, child1; + -- Verify that firing state propagates correctly on creation, too CREATE TABLE trgfire (i int) PARTITION BY RANGE (i); CREATE TABLE trgfire1 PARTITION OF trgfire FOR VALUES FROM (1) TO (10);