From a8796b6aa2f515a7feb151f2fcc825298587192f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
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

