aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/commands/tablecmds.c64
-rw-r--r--src/backend/commands/trigger.c32
-rw-r--r--src/include/commands/trigger.h3
-rw-r--r--src/include/nodes/parsenodes.h1
-rw-r--r--src/test/regress/expected/triggers.out40
-rw-r--r--src/test/regress/sql/triggers.sql11
6 files changed, 117 insertions, 34 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d22dd44712a..70b94bbb397 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -580,7 +580,8 @@ static void ATExecSetRelOptions(Relation rel, List *defList,
AlterTableType operation,
LOCKMODE lockmode);
static void ATExecEnableDisableTrigger(Relation rel, const char *trigname,
- char fires_when, bool skip_system, LOCKMODE lockmode);
+ char fires_when, bool skip_system, bool recurse,
+ LOCKMODE lockmode);
static void ATExecEnableDisableRule(Relation rel, const char *rulename,
char fires_when, LOCKMODE lockmode);
static void ATPrepAddInherit(Relation child_rel);
@@ -4057,9 +4058,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
* be done in this phase. Generally, this phase acquires table locks,
* checks permissions and relkind, and recurses to find child tables.
*
- * ATRewriteCatalogs performs phase 2 for each affected table. (Note that
- * phases 2 and 3 normally do no explicit recursion, since phase 1 already
- * did it --- although some subcommands have to recurse in phase 2 instead.)
+ * ATRewriteCatalogs performs phase 2 for each affected table.
* Certain subcommands need to be performed before others to avoid
* unnecessary conflicts; for example, DROP COLUMN should come before
* ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
@@ -4067,6 +4066,12 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
*
* ATRewriteTables performs phase 3 for those tables that need it.
*
+ * For most subcommand types, phases 2 and 3 do no explicit recursion,
+ * since phase 1 already does it. However, for certain subcommand types
+ * it is only possible to determine how to recurse at phase 2 time; for
+ * those cases, phase 1 sets the cmd->recurse flag (or, in some older coding,
+ * changes the command subtype of a "Recurse" variant XXX to be cleaned up.)
+ *
* Thanks to the magic of MVCC, an error anywhere along the way rolls back
* the whole operation; we don't have to do anything special to clean up.
*
@@ -4487,10 +4492,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
/*
- * Copy the original subcommand for each table. This avoids conflicts
- * when different child tables need to make different parse
- * transformations (for example, the same column may have different column
- * numbers in different children).
+ * Copy the original subcommand for each table, so we can scribble on it.
+ * This avoids conflicts when different child tables need to make
+ * different parse transformations (for example, the same column may have
+ * different column numbers in different children).
*/
cmd = copyObject(cmd);
@@ -4777,8 +4782,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_DisableTrigAll:
case AT_DisableTrigUser:
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
+ /* Set up recursion for phase 2; no other prep needed */
+ if (recurse)
+ cmd->recurse = true;
pass = AT_PASS_MISC;
break;
case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
@@ -5119,35 +5125,51 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
break;
case AT_EnableTrig: /* ENABLE TRIGGER name */
ATExecEnableDisableTrigger(rel, cmd->name,
- TRIGGER_FIRES_ON_ORIGIN, false, lockmode);
+ TRIGGER_FIRES_ON_ORIGIN, false,
+ cmd->recurse,
+ lockmode);
break;
case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
ATExecEnableDisableTrigger(rel, cmd->name,
- TRIGGER_FIRES_ALWAYS, false, lockmode);
+ TRIGGER_FIRES_ALWAYS, false,
+ cmd->recurse,
+ lockmode);
break;
case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
ATExecEnableDisableTrigger(rel, cmd->name,
- TRIGGER_FIRES_ON_REPLICA, false, lockmode);
+ TRIGGER_FIRES_ON_REPLICA, false,
+ cmd->recurse,
+ lockmode);
break;
case AT_DisableTrig: /* DISABLE TRIGGER name */
ATExecEnableDisableTrigger(rel, cmd->name,
- TRIGGER_DISABLED, false, lockmode);
+ TRIGGER_DISABLED, false,
+ cmd->recurse,
+ lockmode);
break;
case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
ATExecEnableDisableTrigger(rel, NULL,
- TRIGGER_FIRES_ON_ORIGIN, false, lockmode);
+ TRIGGER_FIRES_ON_ORIGIN, false,
+ cmd->recurse,
+ lockmode);
break;
case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
ATExecEnableDisableTrigger(rel, NULL,
- TRIGGER_DISABLED, false, lockmode);
+ TRIGGER_DISABLED, false,
+ cmd->recurse,
+ lockmode);
break;
case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
ATExecEnableDisableTrigger(rel, NULL,
- TRIGGER_FIRES_ON_ORIGIN, true, lockmode);
+ TRIGGER_FIRES_ON_ORIGIN, true,
+ cmd->recurse,
+ lockmode);
break;
case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
ATExecEnableDisableTrigger(rel, NULL,
- TRIGGER_DISABLED, true, lockmode);
+ TRIGGER_DISABLED, true,
+ cmd->recurse,
+ lockmode);
break;
case AT_EnableRule: /* ENABLE RULE name */
@@ -14660,9 +14682,11 @@ index_copy_data(Relation rel, RelFileLocator newrlocator)
*/
static void
ATExecEnableDisableTrigger(Relation rel, const char *trigname,
- char fires_when, bool skip_system, LOCKMODE lockmode)
+ char fires_when, bool skip_system, bool recurse,
+ LOCKMODE lockmode)
{
- EnableDisableTrigger(rel, trigname, fires_when, skip_system, lockmode);
+ EnableDisableTrigger(rel, trigname, fires_when, skip_system, recurse,
+ lockmode);
}
/*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b8db53b66de..62a09fb131b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1752,6 +1752,7 @@ renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid,
* enablement/disablement, this also defines when the trigger
* should be fired in session replication roles.
* skip_system: if true, skip "system" triggers (constraint triggers)
+ * recurse: if true, recurse to partitions
*
* Caller should have checked permissions for the table; here we also
* enforce that superuser privilege is required to alter the state of
@@ -1759,7 +1760,8 @@ renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid,
*/
void
EnableDisableTrigger(Relation rel, const char *tgname,
- char fires_when, bool skip_system, LOCKMODE lockmode)
+ char fires_when, bool skip_system, bool recurse,
+ LOCKMODE lockmode)
{
Relation tgrel;
int nkeys;
@@ -1825,6 +1827,34 @@ EnableDisableTrigger(Relation rel, const char *tgname,
changed = true;
}
+ /*
+ * When altering FOR EACH ROW triggers on a partitioned table, do the
+ * same on the partitions as well, unless ONLY is specified.
+ *
+ * Note that we recurse even if we didn't change the trigger above,
+ * because the partitions' copy of the trigger may have a different
+ * value of tgenabled than the parent's trigger and thus might need to
+ * be changed.
+ */
+ if (recurse &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ (TRIGGER_FOR_ROW(oldtrig->tgtype)))
+ {
+ PartitionDesc partdesc = RelationGetPartitionDesc(rel, true);
+ int i;
+
+ for (i = 0; i < partdesc->nparts; i++)
+ {
+ Relation part;
+
+ part = relation_open(partdesc->oids[i], lockmode);
+ EnableDisableTrigger(part, NameStr(oldtrig->tgname),
+ fires_when, skip_system, recurse,
+ lockmode);
+ table_close(part, NoLock); /* keep lock till commit */
+ }
+ }
+
InvokeObjectPostAlterHook(TriggerRelationId,
oldtrig->oid, 0);
}
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 194e8d5bc13..b7b6bd6008c 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -171,7 +171,8 @@ extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok);
extern ObjectAddress renametrig(RenameStmt *stmt);
extern void EnableDisableTrigger(Relation rel, const char *tgname,
- char fires_when, bool skip_system, LOCKMODE lockmode);
+ char fires_when, bool skip_system, bool recurse,
+ LOCKMODE lockmode);
extern void RelationBuildTriggers(Relation relation);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 98fe1abaa28..b3760318562 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2328,6 +2328,7 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
* constraint, or parent table */
DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */
bool missing_ok; /* skip error if missing? */
+ bool recurse; /* exec-time recursion */
} AlterTableCmd;
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 89a34ffbb2d..f131405bc7a 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -2681,24 +2681,42 @@ create table parent (a int) partition by list (a);
create table child1 partition of parent for values in (1);
create trigger tg after insert on parent
for each row execute procedure trig_nothing();
+create trigger tg_stmt after insert on parent
+ for statement execute procedure trig_nothing();
select tgrelid::regclass, tgname, tgenabled from pg_trigger
where tgrelid in ('parent'::regclass, 'child1'::regclass)
order by tgrelid::regclass::text;
- tgrelid | tgname | tgenabled
----------+--------+-----------
- child1 | tg | O
- parent | tg | O
-(2 rows)
+ tgrelid | tgname | tgenabled
+---------+---------+-----------
+ child1 | tg | O
+ parent | tg | O
+ parent | tg_stmt | O
+(3 rows)
-alter table only parent enable always trigger tg;
+alter table only parent enable always trigger tg; -- no recursion because ONLY
+alter table 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)
order by tgrelid::regclass::text;
- tgrelid | tgname | tgenabled
----------+--------+-----------
- child1 | tg | O
- parent | tg | A
-(2 rows)
+ tgrelid | tgname | tgenabled
+---------+---------+-----------
+ child1 | tg | O
+ parent | tg | A
+ parent | tg_stmt | A
+(3 rows)
+
+-- The following is a no-op for the parent trigger but not so
+-- for the child trigger, so recursion should be applied.
+alter table parent enable always trigger tg;
+select tgrelid::regclass, tgname, tgenabled from pg_trigger
+ where tgrelid in ('parent'::regclass, 'child1'::regclass)
+ order by tgrelid::regclass::text;
+ tgrelid | tgname | tgenabled
+---------+---------+-----------
+ child1 | tg | A
+ parent | tg | A
+ parent | tg_stmt | A
+(3 rows)
drop table parent, child1;
-- Verify that firing state propagates correctly on creation, too
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 83cd00f54f0..cb6fc4a90ee 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1865,10 +1865,19 @@ create table parent (a int) partition by list (a);
create table child1 partition of parent for values in (1);
create trigger tg after insert on parent
for each row execute procedure trig_nothing();
+create trigger tg_stmt after insert on parent
+ for statement execute procedure trig_nothing();
select tgrelid::regclass, tgname, tgenabled from pg_trigger
where tgrelid in ('parent'::regclass, 'child1'::regclass)
order by tgrelid::regclass::text;
-alter table only parent enable always trigger tg;
+alter table only parent enable always trigger tg; -- no recursion because ONLY
+alter table 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)
+ order by tgrelid::regclass::text;
+-- The following is a no-op for the parent trigger but not so
+-- for the child trigger, so recursion should be applied.
+alter table parent enable always trigger tg;
select tgrelid::regclass, tgname, tgenabled from pg_trigger
where tgrelid in ('parent'::regclass, 'child1'::regclass)
order by tgrelid::regclass::text;