Re: A row-level trigger on a partitioned table is not created on a sub-partition created later
Alvaro Herrera <[email protected]> Tue, 31 Dec 2019 16:47:59 -0300
| Newsgroups | gmane.comp.db.postgresql.bugs |
|---|---|
| Message-ID | <[email protected]> |
On 2019-Dec-27, Alvaro Herrera wrote: > One thing I just realized is that in next minors' release notes we > should publish a recipe to fix catalogs for existing databases, unless > we go for a fix that doesn't require changing the catalogs -- I don't > know what that would be, though, but maybe it would not be totally > insane to clone even internal triggers in the cases that matter. So, the more I thought about this, the more it seemed that marking those triggers as not internal was the wrong thing to do. So I instead made it clone the internal triggers that seem to matter. This fixes the originally reported problem, and passes the test case I propose. Also, pg_dump continues to work unchanged, and existing database don't need tweaking (excepting those that might already be missing triggers). It would be better to fix master afterwards, by adding a pg_trigger.trgparentid column, which seems like it would be a better answer going forward. -- Ãlvaro Herrera https://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
0001-Add-failed-test-that-shows-the-issue.patch
(text/x-diff, 4 KB)
From d450cff2c0bfa09a5ae4d33de4a1f79001f17429 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera <[email protected]> Date: Fri, 27 Dec 2019 18:56:05 -0300 Subject: [PATCH 1/2] Add failed test that shows the issue --- src/test/regress/expected/triggers.out | 36 ++++++++++++++++---------- src/test/regress/sql/triggers.sql | 4 +++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 1e4053ceed..b91fbd0648 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -1981,15 +1981,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 @@ -2003,12 +2010,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 c21b6c124e..7cd835449c 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -1366,6 +1366,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 -- 2.20.1
0002-Fix-trigger-cloning.patch
(text/x-diff, 2.6 KB)
From 1af46b05c7b0760e3d0a782a869b8e961cc6f96d Mon Sep 17 00:00:00 2001 From: Alvaro Herrera <[email protected]> Date: Tue, 31 Dec 2019 16:39:41 -0300 Subject: [PATCH 2/2] Fix trigger cloning --- src/backend/commands/tablecmds.c | 64 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5b882f80bf..7d0b49e9a4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -15930,6 +15930,53 @@ out: MemoryContextDelete(cxt); } +/* + * doesTriggerDependOnAnotherTrigger + * Checks pg_depend for what the name says + * + * 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 +doesTriggerDependOnAnotherTrigger(Oid trigger_oid) +{ + Relation pg_depend; + ScanKeyData key[2]; + SysScanDesc scan; + HeapTuple tup; + bool found = false; + + pg_depend = table_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); + table_close(pg_depend, AccessShareLock); + + return found; +} + + /* * CloneRowTriggersToPartition * subroutine for ATExecAttachPartition/DefineRelation to create row @@ -15970,8 +16017,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 || + !doesTriggerDependOnAnotherTrigger(trigForm->oid))) continue; /* -- 2.20.1