diff options
author | Alvaro Herrera <alvherre@alvh.no-ip.org> | 2020-04-21 13:57:00 -0400 |
---|---|---|
committer | Alvaro Herrera <alvherre@alvh.no-ip.org> | 2020-04-21 13:57:00 -0400 |
commit | afccd76f1ccef73a341e9b0c6efb29a429f35aa4 (patch) | |
tree | 80984fd38d001ed3dc05e9247b1c9e605c6e8653 /src/backend/commands/tablecmds.c | |
parent | 1542e16f2ca257656717ab8ea263bc310f1b6d51 (diff) | |
download | postgresql-afccd76f1ccef73a341e9b0c6efb29a429f35aa4.tar.gz postgresql-afccd76f1ccef73a341e9b0c6efb29a429f35aa4.zip |
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 86f575948c77. (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
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r-- | src/backend/commands/tablecmds.c | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 037d457c3d4..794ff30fac7 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -547,6 +547,7 @@ static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel, List *partConstraint, bool validate_default); static void CloneRowTriggersToPartition(Relation parent, Relation partition); +static void DropClonedTriggersFromPartition(Oid partitionId); static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name); static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel, RangeVar *name); @@ -16797,6 +16798,9 @@ ATExecDetachPartition(Relation rel, RangeVar *name) } table_close(classRel, RowExclusiveLock); + /* Drop any triggers that were cloned on creation/attach. */ + DropClonedTriggersFromPartition(RelationGetRelid(partRel)); + /* * Detach any foreign keys that are inherited. This includes creating * additional action triggers. @@ -16882,6 +16886,66 @@ ATExecDetachPartition(Relation rel, RangeVar *name) } /* + * DropClonedTriggersFromPartition + * subroutine for ATExecDetachPartition to remove any triggers that were + * cloned to the partition when it was created-as-partition or attached. + * This undoes what CloneRowTriggersToPartition did. + */ +static void +DropClonedTriggersFromPartition(Oid partitionId) +{ + ScanKeyData skey; + SysScanDesc scan; + HeapTuple trigtup; + Relation tgrel; + ObjectAddresses *objects; + + objects = new_object_addresses(); + + /* + * Scan pg_trigger to search for all triggers on this rel. + */ + ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(partitionId)); + tgrel = table_open(TriggerRelationId, RowExclusiveLock); + scan = systable_beginscan(tgrel, TriggerRelidNameIndexId, + true, NULL, 1, &skey); + while (HeapTupleIsValid(trigtup = systable_getnext(scan))) + { + Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup); + ObjectAddress trig; + + /* Ignore triggers that weren't cloned */ + if (!OidIsValid(pg_trigger->tgparentid)) + continue; + + /* + * This is ugly, but necessary: remove the dependency markings on the + * trigger so that it can be removed. + */ + deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid, + TriggerRelationId, + DEPENDENCY_PARTITION_PRI); + deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid, + RelationRelationId, + DEPENDENCY_PARTITION_SEC); + + /* remember this trigger to remove it below */ + ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid); + add_exact_object_address(&trig, objects); + } + + /* make the dependency removal visible to the deletion below */ + CommandCounterIncrement(); + performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); + + /* done */ + free_object_addresses(objects); + systable_endscan(scan); + table_close(tgrel, RowExclusiveLock); +} + +/* * Before acquiring lock on an index, acquire the same lock on the owning * table. */ |