Skip to content

Commit d732148

Browse files
committed
Fix cloning of row triggers to sub-partitions
When row triggers exist in partitioned partitions that are not either part of FKs or deferred unique constraints, they are not correctly cloned to their partitions. That's because they are marked "internal", and those are purposefully skipped when doing the clone triggers dance. Fix by relaxing the condition on which internal triggers are skipped. Amit Langote initially diagnosed the problem and proposed a fix, but I used a different approach. Reported-by: Petr Fedorov Discussion: https://postgr.es/m/6b3f0646-ba8c-b3a9-c62d-1c6651a1920f@phystech.edu
1 parent 98b75c3 commit d732148

File tree

3 files changed

+90
-15
lines changed

3 files changed

+90
-15
lines changed

src/backend/commands/tablecmds.c

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15988,6 +15988,54 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
1598815988
MemoryContextDelete(cxt);
1598915989
}
1599015990

15991+
/*
15992+
* isPartitionTrigger
15993+
* Subroutine for CloneRowTriggersToPartition: determine whether
15994+
* the given trigger has been cloned from another one.
15995+
*
15996+
* We use pg_depend as a proxy for this, since we don't have any direct
15997+
* evidence. This is an ugly hack to cope with a catalog deficiency.
15998+
* Keep away from children. Do not stare with naked eyes. Do not propagate.
15999+
*/
16000+
static bool
16001+
isPartitionTrigger(Oid trigger_oid)
16002+
{
16003+
Relation pg_depend;
16004+
ScanKeyData key[2];
16005+
SysScanDesc scan;
16006+
HeapTuple tup;
16007+
bool found = false;
16008+
16009+
pg_depend = table_open(DependRelationId, AccessShareLock);
16010+
16011+
ScanKeyInit(&key[0], Anum_pg_depend_classid,
16012+
BTEqualStrategyNumber,
16013+
F_OIDEQ,
16014+
ObjectIdGetDatum(TriggerRelationId));
16015+
ScanKeyInit(&key[1], Anum_pg_depend_objid,
16016+
BTEqualStrategyNumber,
16017+
F_OIDEQ,
16018+
ObjectIdGetDatum(trigger_oid));
16019+
16020+
scan = systable_beginscan(pg_depend, DependDependerIndexId,
16021+
true, NULL, 2, key);
16022+
while ((tup = systable_getnext(scan)) != NULL)
16023+
{
16024+
Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(tup);
16025+
16026+
if (dep->refclassid == TriggerRelationId)
16027+
{
16028+
found = true;
16029+
break;
16030+
}
16031+
}
16032+
16033+
systable_endscan(scan);
16034+
table_close(pg_depend, AccessShareLock);
16035+
16036+
return found;
16037+
}
16038+
1599116039
/*
1599216040
* CloneRowTriggersToPartition
1599316041
* subroutine for ATExecAttachPartition/DefineRelation to create row
@@ -16028,8 +16076,21 @@ CloneRowTriggersToPartition(Relation parent, Relation partition)
1602816076
if (!TRIGGER_FOR_ROW(trigForm->tgtype))
1602916077
continue;
1603016078

16031-
/* We don't clone internal triggers, either */
16032-
if (trigForm->tgisinternal)
16079+
/*
16080+
* Internal triggers require careful examination. Ideally, we don't
16081+
* clone them.
16082+
*
16083+
* However, if our parent is a partitioned relation, there might be
16084+
* internal triggers that need cloning. In that case, we must
16085+
* skip clone it if the trigger on parent depends on another trigger.
16086+
*
16087+
* Note we dare not verify that the other trigger belongs to an
16088+
* ancestor relation of our parent, because that creates deadlock
16089+
* opportunities.
16090+
*/
16091+
if (trigForm->tgisinternal &&
16092+
(!parent->rd_rel->relispartition ||
16093+
!isPartitionTrigger(trigForm->oid)))
1603316094
continue;
1603416095

1603516096
/*

src/test/regress/expected/triggers.out

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,15 +1981,22 @@ create trigger trg1 after insert on trigpart for each row execute procedure trig
19811981
create table trigpart2 partition of trigpart for values from (1000) to (2000);
19821982
create table trigpart3 (like trigpart);
19831983
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
1984+
create table trigpart4 partition of trigpart for values from (3000) to (4000) partition by range (a);
1985+
create table trigpart41 partition of trigpart4 for values from (3000) to (3500);
1986+
create table trigpart42 (like trigpart);
1987+
alter table trigpart4 attach partition trigpart42 for values from (3500) to (4000);
19841988
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
19851989
where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text;
1986-
tgrelid | tgname | tgfoid
1987-
-----------+--------+-----------------
1988-
trigpart | trg1 | trigger_nothing
1989-
trigpart1 | trg1 | trigger_nothing
1990-
trigpart2 | trg1 | trigger_nothing
1991-
trigpart3 | trg1 | trigger_nothing
1992-
(4 rows)
1990+
tgrelid | tgname | tgfoid
1991+
------------+--------+-----------------
1992+
trigpart | trg1 | trigger_nothing
1993+
trigpart1 | trg1 | trigger_nothing
1994+
trigpart2 | trg1 | trigger_nothing
1995+
trigpart3 | trg1 | trigger_nothing
1996+
trigpart4 | trg1 | trigger_nothing
1997+
trigpart41 | trg1 | trigger_nothing
1998+
trigpart42 | trg1 | trigger_nothing
1999+
(7 rows)
19932000

19942001
drop trigger trg1 on trigpart1; -- fail
19952002
ERROR: cannot drop trigger trg1 on table trigpart1 because trigger trg1 on table trigpart requires it
@@ -2003,12 +2010,15 @@ HINT: You can drop trigger trg1 on table trigpart instead.
20032010
drop table trigpart2; -- ok, trigger should be gone in that partition
20042011
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
20052012
where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text;
2006-
tgrelid | tgname | tgfoid
2007-
-----------+--------+-----------------
2008-
trigpart | trg1 | trigger_nothing
2009-
trigpart1 | trg1 | trigger_nothing
2010-
trigpart3 | trg1 | trigger_nothing
2011-
(3 rows)
2013+
tgrelid | tgname | tgfoid
2014+
------------+--------+-----------------
2015+
trigpart | trg1 | trigger_nothing
2016+
trigpart1 | trg1 | trigger_nothing
2017+
trigpart3 | trg1 | trigger_nothing
2018+
trigpart4 | trg1 | trigger_nothing
2019+
trigpart41 | trg1 | trigger_nothing
2020+
trigpart42 | trg1 | trigger_nothing
2021+
(6 rows)
20122022

20132023
drop trigger trg1 on trigpart; -- ok, all gone
20142024
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger

src/test/regress/sql/triggers.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,10 @@ create trigger trg1 after insert on trigpart for each row execute procedure trig
13661366
create table trigpart2 partition of trigpart for values from (1000) to (2000);
13671367
create table trigpart3 (like trigpart);
13681368
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
1369+
create table trigpart4 partition of trigpart for values from (3000) to (4000) partition by range (a);
1370+
create table trigpart41 partition of trigpart4 for values from (3000) to (3500);
1371+
create table trigpart42 (like trigpart);
1372+
alter table trigpart4 attach partition trigpart42 for values from (3500) to (4000);
13691373
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
13701374
where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text;
13711375
drop trigger trg1 on trigpart1; -- fail

0 commit comments

Comments
 (0)