Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit ab85566

Browse files
alvherreamitlan
andcommitted
Fix ENABLE/DISABLE TRIGGER to handle recursion correctly
Using ATSimpleRecursion() in ATPrepCmd() to do so as bbb927b did is not correct, because ATPrepCmd() can't distinguish between triggers that may be cloned and those that may not, so would wrongly try to recurse for the latter category of triggers. So this commit restores the code in EnableDisableTrigger() that 86f5759 had added to do the recursion, which would do it only for triggers that may be cloned, that is, row-level triggers. This also changes tablecmds.c such that ATExecCmd() is able to pass the value of ONLY flag down to EnableDisableTrigger() using its new 'recurse' parameter. This also fixes what seems like an oversight of 86f5759 that the recursion to partition triggers would only occur if EnableDisableTrigger() had actually changed the trigger. It is more apt to recurse to inspect partition triggers even if the parent's trigger didn't need to be changed: only then can we be certain that all descendants share the same state afterwards. Backpatch all the way back to 11, like bbb927b. Care is taken not to break ABI compatibility (and that no catversion bump is needed.) Co-authored-by: Amit Langote <amitlangote09@gmail.com> Reviewed-by: Dmitry Koval <d.koval@postgrespro.ru> Discussion: https://postgr.es/m/CA+HiwqG-cZT3XzGAnEgZQLoQbyfJApVwOTQaCaas1mhpf+4V5A@mail.gmail.com
1 parent 23edf0e commit ab85566

File tree

8 files changed

+136
-35
lines changed

8 files changed

+136
-35
lines changed

src/backend/commands/tablecmds.c

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,8 @@ static void ATExecSetRelOptions(Relation rel, List *defList,
523523
AlterTableType operation,
524524
LOCKMODE lockmode);
525525
static void ATExecEnableDisableTrigger(Relation rel, const char *trigname,
526-
char fires_when, bool skip_system, LOCKMODE lockmode);
526+
char fires_when, bool skip_system, bool recurse,
527+
LOCKMODE lockmode);
527528
static void ATExecEnableDisableRule(Relation rel, const char *rulename,
528529
char fires_when, LOCKMODE lockmode);
529530
static void ATPrepAddInherit(Relation child_rel);
@@ -3736,16 +3737,20 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
37363737
* be done in this phase. Generally, this phase acquires table locks,
37373738
* checks permissions and relkind, and recurses to find child tables.
37383739
*
3739-
* ATRewriteCatalogs performs phase 2 for each affected table. (Note that
3740-
* phases 2 and 3 normally do no explicit recursion, since phase 1 already
3741-
* did it --- although some subcommands have to recurse in phase 2 instead.)
3740+
* ATRewriteCatalogs performs phase 2 for each affected table.
37423741
* Certain subcommands need to be performed before others to avoid
37433742
* unnecessary conflicts; for example, DROP COLUMN should come before
37443743
* ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
37453744
* lists, one for each logical "pass" of phase 2.
37463745
*
37473746
* ATRewriteTables performs phase 3 for those tables that need it.
37483747
*
3748+
* For most subcommand types, phases 2 and 3 do no explicit recursion,
3749+
* since phase 1 already does it. However, for certain subcommand types
3750+
* it is only possible to determine how to recurse at phase 2 time; for
3751+
* those cases, phase 1 sets the cmd->recurse flag (or, in some older coding,
3752+
* changes the command subtype of a "Recurse" variant XXX to be cleaned up.)
3753+
*
37493754
* Thanks to the magic of MVCC, an error anywhere along the way rolls back
37503755
* the whole operation; we don't have to do anything special to clean up.
37513756
*
@@ -4144,11 +4149,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
41444149
tab = ATGetQueueEntry(wqueue, rel);
41454150

41464151
/*
4147-
* Copy the original subcommand for each table. This avoids conflicts
4148-
* when different child tables need to make different parse
4149-
* transformations (for example, the same column may have different column
4150-
* numbers in different children). It also ensures that we don't corrupt
4151-
* the original parse tree, in case it is saved in plancache.
4152+
* Copy the original subcommand for each table, so we can scribble on it.
4153+
* This avoids conflicts when different child tables need to make
4154+
* different parse transformations (for example, the same column may have
4155+
* different column numbers in different children). It also ensures that
4156+
* we don't corrupt the original parse tree, in case it is saved in
4157+
* plancache.
41524158
*/
41534159
cmd = copyObject(cmd);
41544160

@@ -4411,8 +4417,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
44114417
case AT_DisableTrigAll:
44124418
case AT_DisableTrigUser:
44134419
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4414-
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
4415-
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
4420+
/* Set up recursion for phase 2; no other prep needed */
4421+
if (recurse)
4422+
cmd->recurse = true;
44164423
pass = AT_PASS_MISC;
44174424
break;
44184425
case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
@@ -4731,35 +4738,51 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
47314738
break;
47324739
case AT_EnableTrig: /* ENABLE TRIGGER name */
47334740
ATExecEnableDisableTrigger(rel, cmd->name,
4734-
TRIGGER_FIRES_ON_ORIGIN, false, lockmode);
4741+
TRIGGER_FIRES_ON_ORIGIN, false,
4742+
cmd->recurse,
4743+
lockmode);
47354744
break;
47364745
case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
47374746
ATExecEnableDisableTrigger(rel, cmd->name,
4738-
TRIGGER_FIRES_ALWAYS, false, lockmode);
4747+
TRIGGER_FIRES_ALWAYS, false,
4748+
cmd->recurse,
4749+
lockmode);
47394750
break;
47404751
case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
47414752
ATExecEnableDisableTrigger(rel, cmd->name,
4742-
TRIGGER_FIRES_ON_REPLICA, false, lockmode);
4753+
TRIGGER_FIRES_ON_REPLICA, false,
4754+
cmd->recurse,
4755+
lockmode);
47434756
break;
47444757
case AT_DisableTrig: /* DISABLE TRIGGER name */
47454758
ATExecEnableDisableTrigger(rel, cmd->name,
4746-
TRIGGER_DISABLED, false, lockmode);
4759+
TRIGGER_DISABLED, false,
4760+
cmd->recurse,
4761+
lockmode);
47474762
break;
47484763
case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
47494764
ATExecEnableDisableTrigger(rel, NULL,
4750-
TRIGGER_FIRES_ON_ORIGIN, false, lockmode);
4765+
TRIGGER_FIRES_ON_ORIGIN, false,
4766+
cmd->recurse,
4767+
lockmode);
47514768
break;
47524769
case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
47534770
ATExecEnableDisableTrigger(rel, NULL,
4754-
TRIGGER_DISABLED, false, lockmode);
4771+
TRIGGER_DISABLED, false,
4772+
cmd->recurse,
4773+
lockmode);
47554774
break;
47564775
case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
47574776
ATExecEnableDisableTrigger(rel, NULL,
4758-
TRIGGER_FIRES_ON_ORIGIN, true, lockmode);
4777+
TRIGGER_FIRES_ON_ORIGIN, true,
4778+
cmd->recurse,
4779+
lockmode);
47594780
break;
47604781
case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
47614782
ATExecEnableDisableTrigger(rel, NULL,
4762-
TRIGGER_DISABLED, true, lockmode);
4783+
TRIGGER_DISABLED, true,
4784+
cmd->recurse,
4785+
lockmode);
47634786
break;
47644787

47654788
case AT_EnableRule: /* ENABLE RULE name */
@@ -13801,9 +13824,11 @@ index_copy_data(Relation rel, RelFileNode newrnode)
1380113824
*/
1380213825
static void
1380313826
ATExecEnableDisableTrigger(Relation rel, const char *trigname,
13804-
char fires_when, bool skip_system, LOCKMODE lockmode)
13827+
char fires_when, bool skip_system, bool recurse,
13828+
LOCKMODE lockmode)
1380513829
{
13806-
EnableDisableTrigger(rel, trigname, fires_when, skip_system, lockmode);
13830+
EnableDisableTriggerNew(rel, trigname, fires_when, skip_system, recurse,
13831+
lockmode);
1380713832
}
1380813833

1380913834
/*

src/backend/commands/trigger.c

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,14 +1468,16 @@ renametrig(RenameStmt *stmt)
14681468
* enablement/disablement, this also defines when the trigger
14691469
* should be fired in session replication roles.
14701470
* skip_system: if true, skip "system" triggers (constraint triggers)
1471+
* recurse: if true, recurse to partitions
14711472
*
14721473
* Caller should have checked permissions for the table; here we also
14731474
* enforce that superuser privilege is required to alter the state of
14741475
* system triggers
14751476
*/
14761477
void
1477-
EnableDisableTrigger(Relation rel, const char *tgname,
1478-
char fires_when, bool skip_system, LOCKMODE lockmode)
1478+
EnableDisableTriggerNew(Relation rel, const char *tgname,
1479+
char fires_when, bool skip_system, bool recurse,
1480+
LOCKMODE lockmode)
14791481
{
14801482
Relation tgrel;
14811483
int nkeys;
@@ -1541,6 +1543,34 @@ EnableDisableTrigger(Relation rel, const char *tgname,
15411543
changed = true;
15421544
}
15431545

1546+
/*
1547+
* When altering FOR EACH ROW triggers on a partitioned table, do the
1548+
* same on the partitions as well, unless ONLY is specified.
1549+
*
1550+
* Note that we recurse even if we didn't change the trigger above,
1551+
* because the partitions' copy of the trigger may have a different
1552+
* value of tgenabled than the parent's trigger and thus might need to
1553+
* be changed.
1554+
*/
1555+
if (recurse &&
1556+
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
1557+
(TRIGGER_FOR_ROW(oldtrig->tgtype)))
1558+
{
1559+
PartitionDesc partdesc = RelationGetPartitionDesc(rel);
1560+
int i;
1561+
1562+
for (i = 0; i < partdesc->nparts; i++)
1563+
{
1564+
Relation part;
1565+
1566+
part = relation_open(partdesc->oids[i], lockmode);
1567+
EnableDisableTriggerNew(part, NameStr(oldtrig->tgname),
1568+
fires_when, skip_system, recurse,
1569+
lockmode);
1570+
table_close(part, NoLock); /* keep lock till commit */
1571+
}
1572+
}
1573+
15441574
InvokeObjectPostAlterHook(TriggerRelationId,
15451575
oldtrig->oid, 0);
15461576
}
@@ -1564,6 +1594,19 @@ EnableDisableTrigger(Relation rel, const char *tgname,
15641594
CacheInvalidateRelcache(rel);
15651595
}
15661596

1597+
/*
1598+
* ABI-compatible wrapper for the above. To keep as close possible to the old
1599+
* behavior, this never recurses. Do not call this function in new code.
1600+
*/
1601+
void
1602+
EnableDisableTrigger(Relation rel, const char *tgname,
1603+
char fires_when, bool skip_system,
1604+
LOCKMODE lockmode)
1605+
{
1606+
EnableDisableTriggerNew(rel, tgname, fires_when, skip_system,
1607+
true, lockmode);
1608+
}
1609+
15671610

15681611
/*
15691612
* Build trigger data to attach to the given relcache entry.

src/backend/nodes/copyfuncs.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3211,6 +3211,7 @@ _copyAlterTableCmd(const AlterTableCmd *from)
32113211
COPY_NODE_FIELD(def);
32123212
COPY_SCALAR_FIELD(behavior);
32133213
COPY_SCALAR_FIELD(missing_ok);
3214+
COPY_SCALAR_FIELD(recurse);
32143215

32153216
return newnode;
32163217
}

src/backend/nodes/equalfuncs.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@ _equalAlterTableCmd(const AlterTableCmd *a, const AlterTableCmd *b)
11031103
COMPARE_NODE_FIELD(def);
11041104
COMPARE_SCALAR_FIELD(behavior);
11051105
COMPARE_SCALAR_FIELD(missing_ok);
1106+
COMPARE_SCALAR_FIELD(recurse);
11061107

11071108
return true;
11081109
}

src/include/commands/trigger.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok);
173173

174174
extern ObjectAddress renametrig(RenameStmt *stmt);
175175

176+
extern void EnableDisableTriggerNew(Relation rel, const char *tgname,
177+
char fires_when, bool skip_system, bool recurse,
178+
LOCKMODE lockmode);
176179
extern void EnableDisableTrigger(Relation rel, const char *tgname,
177180
char fires_when, bool skip_system, LOCKMODE lockmode);
178181

src/include/nodes/parsenodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,7 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
18761876
* constraint, or parent table */
18771877
DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */
18781878
bool missing_ok; /* skip error if missing? */
1879+
bool recurse; /* exec-time recursion */
18791880
} AlterTableCmd;
18801881

18811882

src/test/regress/expected/triggers.out

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2655,24 +2655,42 @@ create table parent (a int) partition by list (a);
26552655
create table child1 partition of parent for values in (1);
26562656
create trigger tg after insert on parent
26572657
for each row execute procedure trig_nothing();
2658+
create trigger tg_stmt after insert on parent
2659+
for statement execute procedure trig_nothing();
26582660
select tgrelid::regclass, tgname, tgenabled from pg_trigger
26592661
where tgrelid in ('parent'::regclass, 'child1'::regclass)
26602662
order by tgrelid::regclass::text;
2661-
tgrelid | tgname | tgenabled
2662-
---------+--------+-----------
2663-
child1 | tg | O
2664-
parent | tg | O
2665-
(2 rows)
2663+
tgrelid | tgname | tgenabled
2664+
---------+---------+-----------
2665+
child1 | tg | O
2666+
parent | tg | O
2667+
parent | tg_stmt | O
2668+
(3 rows)
26662669

2667-
alter table only parent enable always trigger tg;
2670+
alter table only parent enable always trigger tg; -- no recursion because ONLY
2671+
alter table parent enable always trigger tg_stmt; -- no recursion because statement trigger
26682672
select tgrelid::regclass, tgname, tgenabled from pg_trigger
26692673
where tgrelid in ('parent'::regclass, 'child1'::regclass)
26702674
order by tgrelid::regclass::text;
2671-
tgrelid | tgname | tgenabled
2672-
---------+--------+-----------
2673-
child1 | tg | O
2674-
parent | tg | A
2675-
(2 rows)
2675+
tgrelid | tgname | tgenabled
2676+
---------+---------+-----------
2677+
child1 | tg | O
2678+
parent | tg | A
2679+
parent | tg_stmt | A
2680+
(3 rows)
2681+
2682+
-- The following is a no-op for the parent trigger but not so
2683+
-- for the child trigger, so recursion should be applied.
2684+
alter table parent enable always trigger tg;
2685+
select tgrelid::regclass, tgname, tgenabled from pg_trigger
2686+
where tgrelid in ('parent'::regclass, 'child1'::regclass)
2687+
order by tgrelid::regclass::text;
2688+
tgrelid | tgname | tgenabled
2689+
---------+---------+-----------
2690+
child1 | tg | A
2691+
parent | tg | A
2692+
parent | tg_stmt | A
2693+
(3 rows)
26762694

26772695
drop table parent, child1;
26782696
-- Verify that firing state propagates correctly on creation, too

src/test/regress/sql/triggers.sql

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1832,10 +1832,19 @@ create table parent (a int) partition by list (a);
18321832
create table child1 partition of parent for values in (1);
18331833
create trigger tg after insert on parent
18341834
for each row execute procedure trig_nothing();
1835+
create trigger tg_stmt after insert on parent
1836+
for statement execute procedure trig_nothing();
18351837
select tgrelid::regclass, tgname, tgenabled from pg_trigger
18361838
where tgrelid in ('parent'::regclass, 'child1'::regclass)
18371839
order by tgrelid::regclass::text;
1838-
alter table only parent enable always trigger tg;
1840+
alter table only parent enable always trigger tg; -- no recursion because ONLY
1841+
alter table parent enable always trigger tg_stmt; -- no recursion because statement trigger
1842+
select tgrelid::regclass, tgname, tgenabled from pg_trigger
1843+
where tgrelid in ('parent'::regclass, 'child1'::regclass)
1844+
order by tgrelid::regclass::text;
1845+
-- The following is a no-op for the parent trigger but not so
1846+
-- for the child trigger, so recursion should be applied.
1847+
alter table parent enable always trigger tg;
18391848
select tgrelid::regclass, tgname, tgenabled from pg_trigger
18401849
where tgrelid in ('parent'::regclass, 'child1'::regclass)
18411850
order by tgrelid::regclass::text;

0 commit comments

Comments
 (0)