From a8796b6aa2f515a7feb151f2fcc825298587192f Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 19 Jul 2024 15:12:38 +0200 Subject: [PATCH v2] Fix partition detach on tables with FKs to partitioned tables --- src/backend/commands/tablecmds.c | 136 +++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 42 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 721d24783b4..f68ec20cf97 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -547,7 +547,8 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk, static void GetForeignKeyActionTriggers(Relation trigrel, Oid conoid, Oid confrelid, Oid conrelid, Oid *deleteTriggerOid, - Oid *updateTriggerOid); + Oid *updateTriggerOid, + bool missing_ok); static void GetForeignKeyCheckTriggers(Relation trigrel, Oid conoid, Oid confrelid, Oid conrelid, Oid *insertTriggerOid, @@ -10698,7 +10699,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel) */ GetForeignKeyActionTriggers(trigrel, constrOid, constrForm->confrelid, constrForm->conrelid, - &deleteTriggerOid, &updateTriggerOid); + &deleteTriggerOid, &updateTriggerOid, + false); addFkRecurseReferenced(NULL, fkconstraint, @@ -11153,7 +11155,8 @@ static void GetForeignKeyActionTriggers(Relation trigrel, Oid conoid, Oid confrelid, Oid conrelid, Oid *deleteTriggerOid, - Oid *updateTriggerOid) + Oid *updateTriggerOid, + bool missing_ok) { ScanKeyData key; SysScanDesc scan; @@ -11195,10 +11198,10 @@ GetForeignKeyActionTriggers(Relation trigrel, #endif } - if (!OidIsValid(*deleteTriggerOid)) + if (!OidIsValid(*deleteTriggerOid) && !missing_ok) elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u", conoid); - if (!OidIsValid(*updateTriggerOid)) + if (!OidIsValid(*updateTriggerOid) && !missing_ok) elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u", conoid); @@ -19185,10 +19188,15 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent, { ForeignKeyCacheInfo *fk = lfirst(cell); HeapTuple contup; + HeapTuple parentConTup; Form_pg_constraint conform; + Form_pg_constraint parentConForm; Constraint *fkconstraint; + Oid parentConstrOid; Oid insertTriggerOid, updateTriggerOid; + Oid deleteActionTriggerOid, + updateActionTriggerOid; contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid)); if (!HeapTupleIsValid(contup)) @@ -19203,51 +19211,95 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent, continue; } + parentConstrOid = conform->conparentid; + /* unset conparentid and adjust conislocal, coninhcount, etc. */ ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid); /* - * Also, look up the partition's "check" triggers corresponding to the - * constraint being detached and detach them from the parent triggers. + * Search for the partition's check triggers that implement the + * constraint being detached, and make them no longer children of the + * triggers on the parent table. However, if the referenced side is a + * partitioned table, there are no such check triggers (we know that + * the referenced side is partitioned because our constraint row + * points to a partition, whereas our parent constraint points to its + * parent partitioned table.) */ - GetForeignKeyCheckTriggers(trigrel, - fk->conoid, fk->confrelid, fk->conrelid, - &insertTriggerOid, &updateTriggerOid); - Assert(OidIsValid(insertTriggerOid)); - TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid, - RelationGetRelid(partRel)); - Assert(OidIsValid(updateTriggerOid)); - TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid, - RelationGetRelid(partRel)); + Assert(OidIsValid(conform->conparentid)); + parentConTup = SearchSysCache1(CONSTROID, + ObjectIdGetDatum(parentConstrOid)); + if (!HeapTupleIsValid(parentConTup)) + elog(ERROR, "cache lookup failed for constraint %u", + conform->conparentid); + parentConForm = (Form_pg_constraint) GETSTRUCT(parentConTup); + + if (parentConForm->conrelid != conform->conrelid) + { + /* + * Also, look up the partition's "check" triggers corresponding to + * the constraint being detached and detach them from the parent + * triggers. + */ + GetForeignKeyCheckTriggers(trigrel, + fk->conoid, fk->confrelid, fk->conrelid, + &insertTriggerOid, &updateTriggerOid); + Assert(OidIsValid(insertTriggerOid)); + TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid, + RelationGetRelid(partRel)); + Assert(OidIsValid(updateTriggerOid)); + TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid, + RelationGetRelid(partRel)); + } + + ReleaseSysCache(parentConTup); /* - * Make the action triggers on the referenced relation. When this was - * a partition the action triggers pointed to the parent rel (they - * still do), but now we need separate ones of our own. + * There are chances that the action triggers already exists on the + * referenced relation. If they do, . */ - fkconstraint = makeNode(Constraint); - fkconstraint->contype = CONSTRAINT_FOREIGN; - fkconstraint->conname = pstrdup(NameStr(conform->conname)); - fkconstraint->deferrable = conform->condeferrable; - fkconstraint->initdeferred = conform->condeferred; - fkconstraint->location = -1; - fkconstraint->pktable = NULL; - fkconstraint->fk_attrs = NIL; - fkconstraint->pk_attrs = NIL; - fkconstraint->fk_matchtype = conform->confmatchtype; - fkconstraint->fk_upd_action = conform->confupdtype; - fkconstraint->fk_del_action = conform->confdeltype; - fkconstraint->fk_del_set_cols = NIL; - fkconstraint->old_conpfeqop = NIL; - fkconstraint->old_pktable_oid = InvalidOid; - fkconstraint->skip_validation = false; - fkconstraint->initially_valid = true; - - createForeignKeyActionTriggers(partRel, conform->confrelid, - fkconstraint, fk->conoid, - conform->conindid, - InvalidOid, InvalidOid, - NULL, NULL); + GetForeignKeyActionTriggers(trigrel, + fk->conoid, fk->confrelid, fk->conrelid, + &deleteActionTriggerOid, &updateActionTriggerOid, + true); + if (OidIsValid(deleteActionTriggerOid)) + { + Assert(OidIsValid(updateActionTriggerOid)); + TriggerSetParentTrigger(trigrel, deleteActionTriggerOid, InvalidOid, + RelationGetRelid(partRel)); + TriggerSetParentTrigger(trigrel, updateActionTriggerOid, InvalidOid, + RelationGetRelid(partRel)); + } + else + { + /* + * Make the action triggers on the referenced relation. When this + * was a partition the action triggers pointed to the parent rel + * (they still do), but now we need separate ones of our own. + */ + fkconstraint = makeNode(Constraint); + fkconstraint->contype = CONSTRAINT_FOREIGN; + fkconstraint->conname = pstrdup(NameStr(conform->conname)); + fkconstraint->deferrable = conform->condeferrable; + fkconstraint->initdeferred = conform->condeferred; + fkconstraint->location = -1; + fkconstraint->pktable = NULL; + fkconstraint->fk_attrs = NIL; + fkconstraint->pk_attrs = NIL; + fkconstraint->fk_matchtype = conform->confmatchtype; + fkconstraint->fk_upd_action = conform->confupdtype; + fkconstraint->fk_del_action = conform->confdeltype; + fkconstraint->fk_del_set_cols = NIL; + fkconstraint->old_conpfeqop = NIL; + fkconstraint->old_pktable_oid = InvalidOid; + fkconstraint->skip_validation = false; + fkconstraint->initially_valid = true; + + createForeignKeyActionTriggers(partRel, conform->confrelid, + fkconstraint, fk->conoid, + conform->conindid, + InvalidOid, InvalidOid, + NULL, NULL); + } ReleaseSysCache(contup); } -- 2.39.2