@@ -11589,23 +11589,6 @@ tryAttachPartitionForeignKey(List **wqueue,
1158911589 if (!HeapTupleIsValid(partcontup))
1159011590 elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
1159111591 partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11592-
11593- /*
11594- * An error should be raised if the constraint enforceability is different.
11595- * Returning false without raising an error, as we do for other attributes,
11596- * could lead to a duplicate constraint with the same enforceability as the
11597- * parent. While this may be acceptable, it may not be ideal. Therefore,
11598- * it's better to raise an error and allow the user to correct the
11599- * enforceability before proceeding.
11600- */
11601- if (partConstr->conenforced != parentConstr->conenforced)
11602- ereport(ERROR,
11603- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
11604- errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
11605- NameStr(parentConstr->conname),
11606- NameStr(partConstr->conname),
11607- RelationGetRelationName(partition))));
11608-
1160911592 if (OidIsValid(partConstr->conparentid) ||
1161011593 partConstr->condeferrable != parentConstr->condeferrable ||
1161111594 partConstr->condeferred != parentConstr->condeferred ||
@@ -11653,6 +11636,8 @@ AttachPartitionForeignKey(List **wqueue,
1165311636 Oid partConstrFrelid;
1165411637 Oid partConstrRelid;
1165511638 bool parentConstrIsEnforced;
11639+ bool partConstrIsEnforced;
11640+ bool partConstrParentIsSet;
1165611641
1165711642 /* Fetch the parent constraint tuple */
1165811643 parentConstrTup = SearchSysCache1(CONSTROID,
@@ -11668,13 +11653,47 @@ AttachPartitionForeignKey(List **wqueue,
1166811653 if (!HeapTupleIsValid(partcontup))
1166911654 elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
1167011655 partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11656+ partConstrIsEnforced = partConstr->conenforced;
1167111657 partConstrFrelid = partConstr->confrelid;
1167211658 partConstrRelid = partConstr->conrelid;
1167311659
11660+ /*
11661+ * The case where the parent constraint is NOT ENFORCED and the child
11662+ * constraint is ENFORCED is acceptable because the not enforced parent
11663+ * constraint lacks triggers, eliminating any redundancy issues with the
11664+ * enforced child constraint. In this scenario, the child constraint
11665+ * remains enforced, and its trigger is retained, ensuring that
11666+ * referential integrity checks for the child continue as before, even
11667+ * with the parent constraint not enforced. The relationship between the
11668+ * two constraints is preserved by setting the parent constraint, which
11669+ * allows us to locate the child constraint. This becomes important if the
11670+ * parent constraint is later changed to enforced, at which point the
11671+ * necessary trigger will be created for the parent, and any redundancy
11672+ * from these triggers will be appropriately handled.
11673+ */
11674+ if (!parentConstrIsEnforced && partConstrIsEnforced)
11675+ {
11676+ ReleaseSysCache(partcontup);
11677+ ReleaseSysCache(parentConstrTup);
11678+
11679+ ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
11680+ RelationGetRelid(partition));
11681+ CommandCounterIncrement();
11682+
11683+ return;
11684+ }
11685+
1167411686 /*
1167511687 * If the referenced table is partitioned, then the partition we're
1167611688 * attaching now has extra pg_constraint rows and action triggers that are
1167711689 * no longer needed. Remove those.
11690+ *
11691+ * Note that this must be done beforehand, particularly in situations
11692+ * where we might decide to change the constraint to an ENFORCED state
11693+ * which will create the required triggers and add the child constraint to
11694+ * the validation queue. To avoid generating unnecessary triggers and
11695+ * adding them to the validation queue, it is crucial to eliminate any
11696+ * redundant constraints beforehand.
1167811697 */
1167911698 if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
1168011699 {
@@ -11693,6 +11712,53 @@ AttachPartitionForeignKey(List **wqueue,
1169311712 */
1169411713 queueValidation = parentConstr->convalidated && !partConstr->convalidated;
1169511714
11715+ /*
11716+ * The case where the parent constraint is ENFORCED and the child
11717+ * constraint is NOT ENFORCED is not acceptable, as it would violate
11718+ * referential integrity. In such cases, the child constraint will first
11719+ * be enforced before merging it with the enforced parent constraint.
11720+ * Subsequently, removing action triggers, setting up constraint triggers,
11721+ * and handling check triggers for the parent will be managed in the usual
11722+ * manner, similar to how two enforced constraints are merged.
11723+ */
11724+ if (parentConstrIsEnforced && !partConstrIsEnforced)
11725+ {
11726+ ATAlterConstraint *cmdcon = makeNode(ATAlterConstraint);
11727+ Relation conrel;
11728+
11729+ cmdcon->conname = NameStr(partConstr->conname);
11730+ cmdcon->deferrable = partConstr->condeferrable;
11731+ cmdcon->initdeferred = partConstr->condeferred;
11732+ cmdcon->alterEnforceability = true;
11733+ cmdcon->is_enforced = true;
11734+
11735+ conrel = table_open(ConstraintRelationId, RowExclusiveLock);
11736+
11737+ ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, trigrel,
11738+ partConstr->conrelid,
11739+ partConstr->confrelid,
11740+ partcontup, AccessExclusiveLock,
11741+ InvalidOid, InvalidOid, InvalidOid,
11742+ InvalidOid);
11743+
11744+ table_close(conrel, RowExclusiveLock);
11745+
11746+ CommandCounterIncrement();
11747+
11748+ /*
11749+ * No further validation is needed, as changing the constraint to
11750+ * enforced will implicitly trigger the same validation.
11751+ */
11752+ queueValidation = false;
11753+ }
11754+
11755+ /*
11756+ * The constraint parent shouldn't be set beforehand, or if it's already
11757+ * set, it should be the specified parent.
11758+ */
11759+ partConstrParentIsSet = OidIsValid(partConstr->conparentid);
11760+ Assert(!partConstrParentIsSet || partConstr->conparentid == parentConstrOid);
11761+
1169611762 ReleaseSysCache(partcontup);
1169711763 ReleaseSysCache(parentConstrTup);
1169811764
@@ -11705,8 +11771,10 @@ AttachPartitionForeignKey(List **wqueue,
1170511771 DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
1170611772 partConstrRelid);
1170711773
11708- ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
11709- RelationGetRelid(partition));
11774+ /* Skip if the parent is already set */
11775+ if (!partConstrParentIsSet)
11776+ ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
11777+ RelationGetRelid(partition));
1171011778
1171111779 /*
1171211780 * Like the constraint, attach partition's "check" triggers to the
@@ -12306,6 +12374,17 @@ ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
1230612374
1230712375 /* Drop all the triggers */
1230812376 DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
12377+
12378+ /*
12379+ * If the referenced table is partitioned, the child constraint we're
12380+ * changing to NOT ENFORCED may have additional pg_constraint rows and
12381+ * action triggers that remain untouched while this child constraint
12382+ * is attached to the NOT ENFORCED parent. These must now be removed.
12383+ * For more details, see AttachPartitionForeignKey().
12384+ */
12385+ if (OidIsValid(currcon->conparentid) &&
12386+ get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
12387+ RemoveInheritedConstraint(conrel, tgrel, currcon->conrelid, conoid);
1230912388 }
1231012389 else if (changed) /* Create triggers */
1231112390 {
@@ -12631,13 +12710,40 @@ AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
1263112710 true, NULL, 1, &pkey);
1263212711
1263312712 while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
12634- ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
12635- pkrelid, childtup, lockmode,
12636- ReferencedParentDelTrigger,
12637- ReferencedParentUpdTrigger,
12638- ReferencingParentInsTrigger,
12639- ReferencingParentUpdTrigger);
12713+ {
12714+ Form_pg_constraint childcon;
12715+
12716+ childcon = (Form_pg_constraint) GETSTRUCT(childtup);
12717+
12718+ /*
12719+ * When the parent constraint is modified to be ENFORCED, and the
12720+ * child constraint is attached to the parent constraint (which is
12721+ * already ENFORCED), some constraints and action triggers on the
12722+ * child table may become redundant and need to be removed.
12723+ */
12724+ if (cmdcon->is_enforced && childcon->conenforced)
12725+ {
12726+ if (currcon->confrelid == pkrelid)
12727+ {
12728+ Relation rel = table_open(childcon->conrelid, lockmode);
1264012729
12730+ AttachPartitionForeignKey(wqueue, rel, childcon->oid,
12731+ conoid,
12732+ ReferencingParentInsTrigger,
12733+ ReferencingParentUpdTrigger,
12734+ tgrel);
12735+
12736+ table_close(rel, NoLock);
12737+ }
12738+ }
12739+ else
12740+ ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
12741+ pkrelid, childtup, lockmode,
12742+ ReferencedParentDelTrigger,
12743+ ReferencedParentUpdTrigger,
12744+ ReferencingParentInsTrigger,
12745+ ReferencingParentUpdTrigger);
12746+ }
1264112747 systable_endscan(pscan);
1264212748}
1264312749
@@ -20811,7 +20917,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
2081120917 {
2081220918 ForeignKeyCacheInfo *fk = lfirst(cell);
2081320919 HeapTuple contup;
20920+ HeapTuple parentContup;
2081420921 Form_pg_constraint conform;
20922+ Oid parentConstrIsEnforced;
2081520923
2081620924 contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
2081720925 if (!HeapTupleIsValid(contup))
@@ -20830,12 +20938,34 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
2083020938 continue;
2083120939 }
2083220940
20941+ /* Get the enforcibility of the parent constraint */
20942+ parentContup = SearchSysCache1(CONSTROID,
20943+ ObjectIdGetDatum(conform->conparentid));
20944+ if (!HeapTupleIsValid(parentContup))
20945+ elog(ERROR, "cache lookup failed for constraint %u",
20946+ conform->conparentid);
20947+ parentConstrIsEnforced =
20948+ ((Form_pg_constraint) GETSTRUCT(parentContup))->conenforced;
20949+ ReleaseSysCache(parentContup);
20950+
2083320951 /*
2083420952 * The constraint on this table must be marked no longer a child of
2083520953 * the parent's constraint, as do its check triggers.
2083620954 */
2083720955 ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
2083820956
20957+ /*
20958+ * Unsetting the parent is sufficient when the parent constraint is
20959+ * NOT ENFORCED and the child constraint is ENFORCED, as we link them
20960+ * by setting the constraint parent, while leaving the rest unchanged.
20961+ * For more details, see AttachPartitionForeignKey().
20962+ */
20963+ if (!parentConstrIsEnforced && fk->conenforced)
20964+ {
20965+ ReleaseSysCache(contup);
20966+ continue;
20967+ }
20968+
2083920969 /*
2084020970 * Also, look up the partition's "check" triggers corresponding to the
2084120971 * ENFORCED constraint being detached and detach them from the parent
0 commit comments