From 65c76bdb041e60baabe632349833b97faf619a91 Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Mon, 10 Feb 2025 10:59:28 +0530
Subject: [PATCH v19 6/6] Merge the parent and child constraints with differing
 enforcibility.

If an ENFORCED parent constraint is attached to a NOT ENFORCED child
constraint, the child constraint will be made ENFORCED, with
validation applied if the parent constraint is validated as well.
Otherwise, a new ENFORCED constraint (with validation, if the parent
constraint is validated) would need to be created on the child table,
which would be unnecessary if a similar constraint already exists and
can be attached.

On the other hand, having a NOT ENFORCED parent constraint with an
ENFORCED child constraint does not cause any issues, and no changes
are required.

----
NOTE: This patch is intended to reduce the diff noise from the main
patch and is not meant to be committed separately. It should be
squashed with the main patch that adds ENFORCED/NOT ENFORCED.
----
---
 src/backend/commands/tablecmds.c          | 164 ++++++++++++++++++++--
 src/test/regress/expected/foreign_key.out |  74 ++++++++--
 src/test/regress/sql/foreign_key.sql      |  37 ++++-
 3 files changed, 250 insertions(+), 25 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0e126300878..ba6ce9f79b2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11543,7 +11543,6 @@ tryAttachPartitionForeignKey(List **wqueue,
 	if (OidIsValid(partConstr->conparentid) ||
 		partConstr->condeferrable != parentConstr->condeferrable ||
 		partConstr->condeferred != parentConstr->condeferred ||
-		partConstr->conenforced != parentConstr->conenforced ||
 		partConstr->confupdtype != parentConstr->confupdtype ||
 		partConstr->confdeltype != parentConstr->confdeltype ||
 		partConstr->confmatchtype != parentConstr->confmatchtype)
@@ -11588,6 +11587,8 @@ AttachPartitionForeignKey(List **wqueue,
 	Oid			partConstrFrelid;
 	Oid			partConstrRelid;
 	bool		parentConstrIsEnforced;
+	bool		partConstrIsEnforced;
+	bool		partConstrParentIsSet;
 
 	/* Fetch the parent constraint tuple */
 	parentConstrTup = SearchSysCache1(CONSTROID,
@@ -11603,13 +11604,47 @@ AttachPartitionForeignKey(List **wqueue,
 	if (!HeapTupleIsValid(partcontup))
 		elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
 	partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
+	partConstrIsEnforced = partConstr->conenforced;
 	partConstrFrelid = partConstr->confrelid;
 	partConstrRelid = partConstr->conrelid;
 
+	/*
+	 * The case where the parent constraint is NOT ENFORCED and the child
+	 * constraint is ENFORCED is acceptable because the not enforced parent
+	 * constraint lacks triggers, eliminating any redundancy issues with the
+	 * enforced child constraint. In this scenario, the child constraint
+	 * remains enforced, and its trigger is retained, ensuring that
+	 * referential integrity checks for the child continue as before, even
+	 * with the parent constraint not enforced. The relationship between the
+	 * two constraints is preserved by setting the parent constraint, which
+	 * allows us to locate the child constraint. This becomes important if the
+	 * parent constraint is later changed to enforced, at which point the
+	 * necessary trigger will be created for the parent, and any redundancy
+	 * from these triggers will be appropriately handled.
+	 */
+	if (!parentConstrIsEnforced && partConstrIsEnforced)
+	{
+		ReleaseSysCache(partcontup);
+		ReleaseSysCache(parentConstrTup);
+
+		ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
+									  RelationGetRelid(partition));
+		CommandCounterIncrement();
+
+		return;
+	}
+
 	/*
 	 * If the referenced table is partitioned, then the partition we're
 	 * attaching now has extra pg_constraint rows and action triggers that are
 	 * no longer needed.  Remove those.
+	 *
+	 * Note that this must be done beforehand, particularly in situations
+	 * where we might decide to change the constraint to an ENFORCED state
+	 * which will create the required triggers and add the child constraint to
+	 * the validation queue. To avoid generating unnecessary triggers and
+	 * adding them to the validation queue, it is crucial to eliminate any
+	 * redundant constraints beforehand.
 	 */
 	if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
 	{
@@ -11628,6 +11663,53 @@ AttachPartitionForeignKey(List **wqueue,
 	 */
 	queueValidation = parentConstr->convalidated && !partConstr->convalidated;
 
+	/*
+	 * The case where the parent constraint is ENFORCED and the child
+	 * constraint is NOT ENFORCED is not acceptable, as it would violate
+	 * referential integrity. In such cases, the child constraint will first
+	 * be enforced before merging it with the enforced parent constraint.
+	 * Subsequently, removing action triggers, setting up constraint triggers,
+	 * and handling check triggers for the parent will be managed in the usual
+	 * manner, similar to how two enforced constraints are merged.
+	 */
+	if (parentConstrIsEnforced && !partConstrIsEnforced)
+	{
+		ATAlterConstraint *cmdcon = makeNode(ATAlterConstraint);
+		Relation	conrel;
+
+		cmdcon->conname = NameStr(partConstr->conname);
+		cmdcon->deferrable = partConstr->condeferrable;
+		cmdcon->initdeferred = partConstr->condeferred;
+		cmdcon->alterEnforceability = true;
+		cmdcon->is_enforced = true;
+
+		conrel = table_open(ConstraintRelationId, RowExclusiveLock);
+
+		ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, trigrel,
+										partConstr->conrelid,
+										partConstr->confrelid,
+										partcontup, AccessExclusiveLock,
+										InvalidOid, InvalidOid, InvalidOid,
+										InvalidOid);
+
+		table_close(conrel, RowExclusiveLock);
+
+		CommandCounterIncrement();
+
+		/*
+		 * No further validation is needed, as changing the constraint to
+		 * enforced will implicitly trigger the same validation.
+		 */
+		queueValidation = false;
+	}
+
+	/*
+	 * The constraint parent shouldn't be set beforehand, or if it's already
+	 * set, it should be the specified parent.
+	 */
+	partConstrParentIsSet = OidIsValid(partConstr->conparentid);
+	Assert(!partConstrParentIsSet || partConstr->conparentid == parentConstrOid);
+
 	ReleaseSysCache(partcontup);
 	ReleaseSysCache(parentConstrTup);
 
@@ -11640,8 +11722,10 @@ AttachPartitionForeignKey(List **wqueue,
 	DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
 									 partConstrRelid);
 
-	ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
-								  RelationGetRelid(partition));
+	/* Skip if the parent is already set */
+	if (!partConstrParentIsSet)
+		ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
+									  RelationGetRelid(partition));
 
 	/*
 	 * Like the constraint, attach partition's "check" triggers to the
@@ -12241,6 +12325,17 @@ ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
 
 		/* Drop all the triggers */
 		DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
+
+		/*
+		 * If the referenced table is partitioned, the child constraint we're
+		 * changing to NOT ENFORCED may have additional pg_constraint rows and
+		 * action triggers that remain untouched while this child constraint
+		 * is attached to the NOT ENFORCED parent. These must now be removed.
+		 * For more details, see AttachPartitionForeignKey().
+		 */
+		if (OidIsValid(currcon->conparentid) &&
+			get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
+			RemoveInheritedConstraint(conrel, tgrel, currcon->conrelid, conoid);
 	}
 	else if (changed)			/* Create triggers */
 	{
@@ -12566,13 +12661,40 @@ AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
 							   true, NULL, 1, &pkey);
 
 	while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
-		ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
-										pkrelid, childtup, lockmode,
-										ReferencedParentDelTrigger,
-										ReferencedParentUpdTrigger,
-										ReferencingParentInsTrigger,
-										ReferencingParentUpdTrigger);
+	{
+		Form_pg_constraint childcon;
 
+		childcon = (Form_pg_constraint) GETSTRUCT(childtup);
+
+		/*
+		 * When the parent constraint is modified to be ENFORCED, and the
+		 * child constraint is attached to the parent constraint (which is
+		 * already ENFORCED), some constraints and action triggers on the
+		 * child table may become redundant and need to be removed.
+		 */
+		if (cmdcon->is_enforced && childcon->conenforced)
+		{
+			if (currcon->confrelid == pkrelid)
+			{
+				Relation	rel = table_open(childcon->conrelid, lockmode);
+
+				AttachPartitionForeignKey(wqueue, rel, childcon->oid,
+										  conoid,
+										  ReferencingParentInsTrigger,
+										  ReferencingParentUpdTrigger,
+										  tgrel);
+
+				table_close(rel, NoLock);
+			}
+		}
+		else
+			ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
+											pkrelid, childtup, lockmode,
+											ReferencedParentDelTrigger,
+											ReferencedParentUpdTrigger,
+											ReferencingParentInsTrigger,
+											ReferencingParentUpdTrigger);
+	}
 	systable_endscan(pscan);
 }
 
@@ -20746,7 +20868,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
 	{
 		ForeignKeyCacheInfo *fk = lfirst(cell);
 		HeapTuple	contup;
+		HeapTuple	parentContup;
 		Form_pg_constraint conform;
+		Oid			parentConstrIsEnforced;
 
 		contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
 		if (!HeapTupleIsValid(contup))
@@ -20765,12 +20889,34 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
 			continue;
 		}
 
+		/* Get the enforcibility of the parent constraint */
+		parentContup = SearchSysCache1(CONSTROID,
+									   ObjectIdGetDatum(conform->conparentid));
+		if (!HeapTupleIsValid(parentContup))
+			elog(ERROR, "cache lookup failed for constraint %u",
+				 conform->conparentid);
+		parentConstrIsEnforced =
+			((Form_pg_constraint) GETSTRUCT(parentContup))->conenforced;
+		ReleaseSysCache(parentContup);
+
 		/*
 		 * The constraint on this table must be marked no longer a child of
 		 * the parent's constraint, as do its check triggers.
 		 */
 		ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
 
+		/*
+		 * Unsetting the parent is sufficient when the parent constraint is
+		 * NOT ENFORCED and the child constraint is ENFORCED, as we link them
+		 * by setting the constraint parent, while leaving the rest unchanged.
+		 * For more details, see AttachPartitionForeignKey().
+		 */
+		if (!parentConstrIsEnforced && fk->conenforced)
+		{
+			ReleaseSysCache(contup);
+			continue;
+		}
+
 		/*
 		 * Also, look up the partition's "check" triggers corresponding to the
 		 * ENFORCED constraint being detached and detach them from the parent
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 78ccf940647..84eddbd9e2c 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -1670,8 +1670,6 @@ ALTER TABLE fk_partitioned_fk ADD CONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGN
 	REFERENCES fk_notpartitioned_pk NOT ENFORCED;
 CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int);
 ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2;
-ALTER TABLE fk_partitioned_fk_2 ADD CONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGN KEY (a, b)
-	REFERENCES fk_notpartitioned_pk NOT ENFORCED;
 ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000);
 ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED;
 CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
@@ -1680,8 +1678,65 @@ ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
 	DROP COLUMN fdrop3, DROP COLUMN fdrop4;
 CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
 CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1);
+-- Merge the not-enforced parent constraint with the enforced and not-enforced child constraints
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+-- Merge the non-enforced parent constraint with both the enforced and
+-- non-enforced child constraints, where the referenced table is partitioned.
+CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
+CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
+-- Merge the enforced parent constraint with the enforced and not-enforced child constraints.
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+             conname             | conenforced | convalidated |       conrelid        |      confrelid       
+---------------------------------+-------------+--------------+-----------------------+----------------------
+ fk_partitioned_fk_3_0_a_b_fkey  | t           | t            | fk_partitioned_fk_3_0 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey  | f           | f            | fk_partitioned_fk_3_1 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_a_b_fkey    | f           | f            | fk_partitioned_fk_3   | fk_notpartitioned_pk
+ fk_partitioned_fk_3_0_a_b_fkey1 | t           | t            | fk_partitioned_fk_3_0 | fk_partitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey1 | t           | t            | fk_partitioned_fk_3_1 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey1   | t           | t            | fk_partitioned_fk_3   | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey2   | t           | t            | fk_partitioned_fk_3   | fk_partitioned_pk_1
+(7 rows)
+
+ALTER TABLE fk_partitioned_fk_3 DETACH PARTITION fk_partitioned_fk_3_0;
+ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_0 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+             conname             | conenforced | convalidated |       conrelid        |      confrelid       
+---------------------------------+-------------+--------------+-----------------------+----------------------
+ fk_partitioned_fk_3_0_a_b_fkey  | t           | t            | fk_partitioned_fk_3_0 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey  | f           | f            | fk_partitioned_fk_3_1 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_a_b_fkey    | f           | f            | fk_partitioned_fk_3   | fk_notpartitioned_pk
+ fk_partitioned_fk_3_0_a_b_fkey1 | t           | t            | fk_partitioned_fk_3_0 | fk_partitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey1 | t           | t            | fk_partitioned_fk_3_1 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey1   | t           | t            | fk_partitioned_fk_3   | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey2   | t           | t            | fk_partitioned_fk_3   | fk_partitioned_pk_1
+(7 rows)
+
+ALTER TABLE fk_partitioned_fk_3 ALTER CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 NOT ENFORCED;
+-- Merging an enforced parent constraint (validated) with a not-enforced child
+-- constraint will implicitly change the child constraint to enforced and apply
+-- the validation as well.
 ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3
   FOR VALUES FROM (2000,2000) TO (3000,3000);
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+             conname             | conenforced | convalidated |       conrelid        |      confrelid       
+---------------------------------+-------------+--------------+-----------------------+----------------------
+ fk_partitioned_fk_3_0_a_b_fkey  | t           | t            | fk_partitioned_fk_3_0 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey  | t           | t            | fk_partitioned_fk_3_1 | fk_notpartitioned_pk
+ fk_partitioned_fk_3_a_b_fkey    | t           | t            | fk_partitioned_fk_3   | fk_notpartitioned_pk
+ fk_partitioned_fk_3_0_a_b_fkey1 | f           | f            | fk_partitioned_fk_3_0 | fk_partitioned_pk
+ fk_partitioned_fk_3_1_a_b_fkey1 | f           | f            | fk_partitioned_fk_3_1 | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey1   | f           | f            | fk_partitioned_fk_3   | fk_partitioned_pk
+ fk_partitioned_fk_3_a_b_fkey2   | f           | f            | fk_partitioned_fk_3   | fk_partitioned_pk_1
+(7 rows)
+
 -- Creating a foreign key with ONLY on a partitioned table referencing
 -- a non-partitioned table fails.
 ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b)
@@ -1702,16 +1757,16 @@ INSERT INTO fk_partitioned_fk_2 (a,b) VALUES (1500, 1501);
 ERROR:  insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
 DETAIL:  Key (a, b)=(1500, 1501) is not present in table "fk_notpartitioned_pk".
 INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502);
-ERROR:  insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR:  insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey"
 DETAIL:  Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk".
 INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2500, 2502);
-ERROR:  insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR:  insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey"
 DETAIL:  Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk".
 INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
-ERROR:  insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR:  insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_3_0_a_b_fkey"
 DETAIL:  Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk".
 INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2501, 2503);
-ERROR:  insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR:  insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_3_0_a_b_fkey"
 DETAIL:  Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk".
 -- but if we insert the values that make them valid, then they work
 INSERT INTO fk_notpartitioned_pk VALUES (500, 501), (1500, 1501),
@@ -1722,7 +1777,7 @@ INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502);
 INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
 -- this update fails because there is no referenced row
 UPDATE fk_partitioned_fk SET a = a + 1 WHERE a = 2501;
-ERROR:  insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey"
+ERROR:  insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey"
 DETAIL:  Key (a, b)=(2502, 2503) is not present in table "fk_notpartitioned_pk".
 -- but we can fix it thusly:
 INSERT INTO fk_notpartitioned_pk (a,b) VALUES (2502, 2503);
@@ -1865,8 +1920,6 @@ WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid::regclass:
 
 DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
 -- NOT VALID foreign key on a non-partitioned table referencing a partitioned table
-CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
-CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
 CREATE TABLE fk_notpartitioned_fk (b int, a int);
 ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
 -- Constraint will be invalid.
@@ -2082,7 +2135,7 @@ CREATE TABLE fk_partitioned_fk_2 (b int, a int,
 	FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED);
 BEGIN;
 ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
--- should be having two constraints
+-- should only have one constraint
 \d fk_partitioned_fk_2
         Table "public.fk_partitioned_fk_2"
  Column |  Type   | Collation | Nullable | Default 
@@ -2091,7 +2144,6 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN
  a      | integer |           |          | 
 Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
 Foreign-key constraints:
-    "fk_partitioned_fk_2_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED
     TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
 
 DROP TABLE fk_partitioned_fk_2;
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index 83de3338292..b4bc2a5531e 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -1245,8 +1245,6 @@ ALTER TABLE fk_partitioned_fk ADD CONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGN
 	REFERENCES fk_notpartitioned_pk NOT ENFORCED;
 CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int);
 ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2;
-ALTER TABLE fk_partitioned_fk_2 ADD CONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGN KEY (a, b)
-	REFERENCES fk_notpartitioned_pk NOT ENFORCED;
 ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000);
 ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED;
 CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
@@ -1255,9 +1253,40 @@ ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
 	DROP COLUMN fdrop3, DROP COLUMN fdrop4;
 CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
 CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1);
+-- Merge the not-enforced parent constraint with the enforced and not-enforced child constraints
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED;
+
+-- Merge the non-enforced parent constraint with both the enforced and
+-- non-enforced child constraints, where the referenced table is partitioned.
+CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
+CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
+-- Merge the enforced parent constraint with the enforced and not-enforced child constraints.
+ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT ENFORCED;
+ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED;
+
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+
+ALTER TABLE fk_partitioned_fk_3 DETACH PARTITION fk_partitioned_fk_3_0;
+ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_0 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
+
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+
+ALTER TABLE fk_partitioned_fk_3 ALTER CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 NOT ENFORCED;
+
+-- Merging an enforced parent constraint (validated) with a not-enforced child
+-- constraint will implicitly change the child constraint to enforced and apply
+-- the validation as well.
 ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3
   FOR VALUES FROM (2000,2000) TO (3000,3000);
 
+SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass
+FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid;
+
 -- Creating a foreign key with ONLY on a partitioned table referencing
 -- a non-partitioned table fails.
 ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b)
@@ -1384,8 +1413,6 @@ WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid::regclass:
 DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
 
 -- NOT VALID foreign key on a non-partitioned table referencing a partitioned table
-CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
-CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
 CREATE TABLE fk_notpartitioned_fk (b int, a int);
 ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
 
@@ -1526,7 +1553,7 @@ CREATE TABLE fk_partitioned_fk_2 (b int, a int,
 	FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED);
 BEGIN;
 ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
--- should be having two constraints
+-- should only have one constraint
 \d fk_partitioned_fk_2
 DROP TABLE fk_partitioned_fk_2;
 ROLLBACK;
-- 
2.43.5

