aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlvaro Herrera <alvherre@alvh.no-ip.org>2020-01-02 17:04:24 -0300
committerAlvaro Herrera <alvherre@alvh.no-ip.org>2020-01-02 17:04:24 -0300
commitadc9cb6f26f785d1e62a0bd86d69974aab1bf6e5 (patch)
tree0da0f5d5eb21d272b36cd525b323cf7215a8e92e
parentf565f530edcc4728ed004b7c75b876168df2daa0 (diff)
downloadpostgresql-adc9cb6f26f785d1e62a0bd86d69974aab1bf6e5.tar.gz
postgresql-adc9cb6f26f785d1e62a0bd86d69974aab1bf6e5.zip
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
-rw-r--r--src/backend/commands/tablecmds.c65
-rw-r--r--src/test/regress/expected/triggers.out36
-rw-r--r--src/test/regress/sql/triggers.sql4
3 files changed, 90 insertions, 15 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d207bc299f9..a120a18f029 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15243,6 +15243,54 @@ out:
}
/*
+ * isPartitionTrigger
+ * Subroutine for CloneRowTriggersToPartition: determine whether
+ * the given trigger has been cloned from another one.
+ *
+ * We use pg_depend as a proxy for this, since we don't have any direct
+ * evidence. This is an ugly hack to cope with a catalog deficiency.
+ * Keep away from children. Do not stare with naked eyes. Do not propagate.
+ */
+static bool
+isPartitionTrigger(Oid trigger_oid)
+{
+ Relation pg_depend;
+ ScanKeyData key[2];
+ SysScanDesc scan;
+ HeapTuple tup;
+ bool found = false;
+
+ pg_depend = heap_open(DependRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0], Anum_pg_depend_classid,
+ BTEqualStrategyNumber,
+ F_OIDEQ,
+ ObjectIdGetDatum(TriggerRelationId));
+ ScanKeyInit(&key[1], Anum_pg_depend_objid,
+ BTEqualStrategyNumber,
+ F_OIDEQ,
+ ObjectIdGetDatum(trigger_oid));
+
+ scan = systable_beginscan(pg_depend, DependDependerIndexId,
+ true, NULL, 2, key);
+ while ((tup = systable_getnext(scan)) != NULL)
+ {
+ Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(tup);
+
+ if (dep->refclassid == TriggerRelationId)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ systable_endscan(scan);
+ heap_close(pg_depend, AccessShareLock);
+
+ return found;
+}
+
+/*
* CloneRowTriggersToPartition
* subroutine for ATExecAttachPartition/DefineRelation to create row
* triggers on partitions
@@ -15282,8 +15330,21 @@ CloneRowTriggersToPartition(Relation parent, Relation partition)
if (!TRIGGER_FOR_ROW(trigForm->tgtype))
continue;
- /* We don't clone internal triggers, either */
- if (trigForm->tgisinternal)
+ /*
+ * Internal triggers require careful examination. Ideally, we don't
+ * clone them.
+ *
+ * However, if our parent is a partitioned relation, there might be
+ * internal triggers that need cloning. In that case, we must
+ * skip clone it if the trigger on parent depends on another trigger.
+ *
+ * Note we dare not verify that the other trigger belongs to an
+ * ancestor relation of our parent, because that creates deadlock
+ * opportunities.
+ */
+ if (trigForm->tgisinternal &&
+ (!parent->rd_rel->relispartition ||
+ !isPartitionTrigger(HeapTupleGetOid(tuple))))
continue;
/*
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 8d8f4f2f153..cd9bdc8c4b8 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1979,15 +1979,22 @@ create trigger trg1 after insert on trigpart for each row execute procedure trig
create table trigpart2 partition of trigpart for values from (1000) to (2000);
create table trigpart3 (like trigpart);
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
+create table trigpart4 partition of trigpart for values from (3000) to (4000) partition by range (a);
+create table trigpart41 partition of trigpart4 for values from (3000) to (3500);
+create table trigpart42 (like trigpart);
+alter table trigpart4 attach partition trigpart42 for values from (3500) to (4000);
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text;
- tgrelid | tgname | tgfoid
------------+--------+-----------------
- trigpart | trg1 | trigger_nothing
- trigpart1 | trg1 | trigger_nothing
- trigpart2 | trg1 | trigger_nothing
- trigpart3 | trg1 | trigger_nothing
-(4 rows)
+ tgrelid | tgname | tgfoid
+------------+--------+-----------------
+ trigpart | trg1 | trigger_nothing
+ trigpart1 | trg1 | trigger_nothing
+ trigpart2 | trg1 | trigger_nothing
+ trigpart3 | trg1 | trigger_nothing
+ trigpart4 | trg1 | trigger_nothing
+ trigpart41 | trg1 | trigger_nothing
+ trigpart42 | trg1 | trigger_nothing
+(7 rows)
drop trigger trg1 on trigpart1; -- fail
ERROR: cannot drop trigger trg1 on table trigpart1 because trigger trg1 on table trigpart requires it
@@ -2001,12 +2008,15 @@ HINT: You can drop trigger trg1 on table trigpart instead.
drop table trigpart2; -- ok, trigger should be gone in that partition
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text;
- tgrelid | tgname | tgfoid
------------+--------+-----------------
- trigpart | trg1 | trigger_nothing
- trigpart1 | trg1 | trigger_nothing
- trigpart3 | trg1 | trigger_nothing
-(3 rows)
+ tgrelid | tgname | tgfoid
+------------+--------+-----------------
+ trigpart | trg1 | trigger_nothing
+ trigpart1 | trg1 | trigger_nothing
+ trigpart3 | trg1 | trigger_nothing
+ trigpart4 | trg1 | trigger_nothing
+ trigpart41 | trg1 | trigger_nothing
+ trigpart42 | trg1 | trigger_nothing
+(6 rows)
drop trigger trg1 on trigpart; -- ok, all gone
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 337fbb5e4da..b0cac89b74c 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1371,6 +1371,10 @@ create trigger trg1 after insert on trigpart for each row execute procedure trig
create table trigpart2 partition of trigpart for values from (1000) to (2000);
create table trigpart3 (like trigpart);
alter table trigpart attach partition trigpart3 for values from (2000) to (3000);
+create table trigpart4 partition of trigpart for values from (3000) to (4000) partition by range (a);
+create table trigpart41 partition of trigpart4 for values from (3000) to (3500);
+create table trigpart42 (like trigpart);
+alter table trigpart4 attach partition trigpart42 for values from (3500) to (4000);
select tgrelid::regclass, tgname, tgfoid::regproc from pg_trigger
where tgrelid::regclass::text like 'trigpart%' order by tgrelid::regclass::text;
drop trigger trg1 on trigpart1; -- fail