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

Commit afccd76

Browse files
committed
Fix detaching partitions with cloned row triggers
When a partition is detached, any triggers that had been cloned from its parent were not properly disentangled from its parent triggers. This resulted in triggers that could not be dropped because they depended on the trigger in the trigger in the no-longer-parent table: ALTER TABLE t DETACH PARTITION t1; DROP TRIGGER trig ON t1; ERROR: cannot drop trigger trig on table t1 because trigger trig on table t requires it HINT: You can drop trigger trig on table t instead. Moreover the table can no longer be re-attached to its parent, because the trigger name is already taken: ALTER TABLE t ATTACH PARTITION t1 FOR VALUES FROM (1)TO(2); ERROR: trigger "trig" for relation "t1" already exists The former is a bug introduced in commit 86f5759. (The latter is not necessarily a bug, but it makes the bug more uncomfortable.) To avoid the complexity that would be needed to tell whether the trigger has a local definition that has to be merged with the one coming from the parent table, establish the behavior that the trigger is removed when the table is detached. Backpatch to pg11. Author: Justin Pryzby <pryzby@telsasoft.com> Reviewed-by: Amit Langote <amitlangote09@gmail.com> Reviewed-by: Álvaro Herrera <alvherre@alvh.no-ip.org> Discussion: https://www.postgresql.org/message-id/flat/20200408152412.GZ2228@telsasoft.com
1 parent 1542e16 commit afccd76

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed

doc/src/sgml/ref/create_trigger.sgml

+1
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
526526
Creating a row-level trigger on a partitioned table will cause identical
527527
triggers to be created in all its existing partitions; and any partitions
528528
created or attached later will contain an identical trigger, too.
529+
If the partition is detached from its parent, the trigger is removed.
529530
Triggers on partitioned tables may not be <literal>INSTEAD OF</literal>.
530531
</para>
531532

src/backend/commands/tablecmds.c

+64
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
547547
List *partConstraint,
548548
bool validate_default);
549549
static void CloneRowTriggersToPartition(Relation parent, Relation partition);
550+
static void DropClonedTriggersFromPartition(Oid partitionId);
550551
static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
551552
static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
552553
RangeVar *name);
@@ -16797,6 +16798,9 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
1679716798
}
1679816799
table_close(classRel, RowExclusiveLock);
1679916800

16801+
/* Drop any triggers that were cloned on creation/attach. */
16802+
DropClonedTriggersFromPartition(RelationGetRelid(partRel));
16803+
1680016804
/*
1680116805
* Detach any foreign keys that are inherited. This includes creating
1680216806
* additional action triggers.
@@ -16881,6 +16885,66 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
1688116885
return address;
1688216886
}
1688316887

16888+
/*
16889+
* DropClonedTriggersFromPartition
16890+
* subroutine for ATExecDetachPartition to remove any triggers that were
16891+
* cloned to the partition when it was created-as-partition or attached.
16892+
* This undoes what CloneRowTriggersToPartition did.
16893+
*/
16894+
static void
16895+
DropClonedTriggersFromPartition(Oid partitionId)
16896+
{
16897+
ScanKeyData skey;
16898+
SysScanDesc scan;
16899+
HeapTuple trigtup;
16900+
Relation tgrel;
16901+
ObjectAddresses *objects;
16902+
16903+
objects = new_object_addresses();
16904+
16905+
/*
16906+
* Scan pg_trigger to search for all triggers on this rel.
16907+
*/
16908+
ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
16909+
F_OIDEQ, ObjectIdGetDatum(partitionId));
16910+
tgrel = table_open(TriggerRelationId, RowExclusiveLock);
16911+
scan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
16912+
true, NULL, 1, &skey);
16913+
while (HeapTupleIsValid(trigtup = systable_getnext(scan)))
16914+
{
16915+
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup);
16916+
ObjectAddress trig;
16917+
16918+
/* Ignore triggers that weren't cloned */
16919+
if (!OidIsValid(pg_trigger->tgparentid))
16920+
continue;
16921+
16922+
/*
16923+
* This is ugly, but necessary: remove the dependency markings on the
16924+
* trigger so that it can be removed.
16925+
*/
16926+
deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
16927+
TriggerRelationId,
16928+
DEPENDENCY_PARTITION_PRI);
16929+
deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
16930+
RelationRelationId,
16931+
DEPENDENCY_PARTITION_SEC);
16932+
16933+
/* remember this trigger to remove it below */
16934+
ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid);
16935+
add_exact_object_address(&trig, objects);
16936+
}
16937+
16938+
/* make the dependency removal visible to the deletion below */
16939+
CommandCounterIncrement();
16940+
performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
16941+
16942+
/* done */
16943+
free_object_addresses(objects);
16944+
systable_endscan(scan);
16945+
table_close(tgrel, RowExclusiveLock);
16946+
}
16947+
1688416948
/*
1688516949
* Before acquiring lock on an index, acquire the same lock on the owning
1688616950
* table.

src/test/regress/expected/triggers.out

+45
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,51 @@ select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
20232023
---------+--------+--------
20242024
(0 rows)
20252025

2026+
-- check detach behavior
2027+
create trigger trg1 after insert on trigpart for each row execute procedure trigger_nothing();
2028+
\d trigpart3
2029+
Table "public.trigpart3"
2030+
Column | Type | Collation | Nullable | Default
2031+
--------+---------+-----------+----------+---------
2032+
a | integer | | |
2033+
b | integer | | |
2034+
Partition of: trigpart FOR VALUES FROM (2000) TO (3000)
2035+
Triggers:
2036+
trg1 AFTER INSERT ON trigpart3 FOR EACH ROW EXECUTE FUNCTION trigger_nothing()
2037+
2038+
alter table trigpart detach partition trigpart3;
2039+
drop trigger trg1 on trigpart3; -- fail due to "does not exist"
2040+
ERROR: trigger "trg1" for table "trigpart3" does not exist
2041+
alter table trigpart detach partition trigpart4;
2042+
drop trigger trg1 on trigpart41; -- fail due to "does not exist"
2043+
ERROR: trigger "trg1" for table "trigpart41" does not exist
2044+
drop table trigpart4;
2045+
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
2046+
alter table trigpart detach partition trigpart3;
2047+
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
2048+
drop table trigpart3;
2049+
select tgrelid::regclass::text, tgname, tgfoid::regproc, tgenabled, tgisinternal from pg_trigger
2050+
where tgname ~ '^trg1' order by 1;
2051+
tgrelid | tgname | tgfoid | tgenabled | tgisinternal
2052+
-----------+--------+-----------------+-----------+--------------
2053+
trigpart | trg1 | trigger_nothing | O | f
2054+
trigpart1 | trg1 | trigger_nothing | O | t
2055+
(2 rows)
2056+
2057+
create table trigpart3 (like trigpart);
2058+
create trigger trg1 after insert on trigpart3 for each row execute procedure trigger_nothing();
2059+
\d trigpart3
2060+
Table "public.trigpart3"
2061+
Column | Type | Collation | Nullable | Default
2062+
--------+---------+-----------+----------+---------
2063+
a | integer | | |
2064+
b | integer | | |
2065+
Triggers:
2066+
trg1 AFTER INSERT ON trigpart3 FOR EACH ROW EXECUTE FUNCTION trigger_nothing()
2067+
2068+
alter table trigpart attach partition trigpart3 FOR VALUES FROM (2000) to (3000); -- fail
2069+
ERROR: trigger "trg1" for relation "trigpart3" already exists
2070+
drop table trigpart3;
20262071
drop table trigpart;
20272072
drop function trigger_nothing();
20282073
--

src/test/regress/sql/triggers.sql

+21
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,27 @@ drop trigger trg1 on trigpart; -- ok, all gone
13801380
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
13811381
where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text;
13821382

1383+
-- check detach behavior
1384+
create trigger trg1 after insert on trigpart for each row execute procedure trigger_nothing();
1385+
\d trigpart3
1386+
alter table trigpart detach partition trigpart3;
1387+
drop trigger trg1 on trigpart3; -- fail due to "does not exist"
1388+
alter table trigpart detach partition trigpart4;
1389+
drop trigger trg1 on trigpart41; -- fail due to "does not exist"
1390+
drop table trigpart4;
1391+
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
1392+
alter table trigpart detach partition trigpart3;
1393+
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
1394+
drop table trigpart3;
1395+
1396+
select tgrelid::regclass::text, tgname, tgfoid::regproc, tgenabled, tgisinternal from pg_trigger
1397+
where tgname ~ '^trg1' order by 1;
1398+
create table trigpart3 (like trigpart);
1399+
create trigger trg1 after insert on trigpart3 for each row execute procedure trigger_nothing();
1400+
\d trigpart3
1401+
alter table trigpart attach partition trigpart3 FOR VALUES FROM (2000) to (3000); -- fail
1402+
drop table trigpart3;
1403+
13831404
drop table trigpart;
13841405
drop function trigger_nothing();
13851406

0 commit comments

Comments
 (0)