@@ -11589,23 +11589,6 @@ tryAttachPartitionForeignKey(List **wqueue,
11589
11589
if (!HeapTupleIsValid(partcontup))
11590
11590
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
11591
11591
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
-
11609
11592
if (OidIsValid(partConstr->conparentid) ||
11610
11593
partConstr->condeferrable != parentConstr->condeferrable ||
11611
11594
partConstr->condeferred != parentConstr->condeferred ||
@@ -11653,6 +11636,8 @@ AttachPartitionForeignKey(List **wqueue,
11653
11636
Oid partConstrFrelid;
11654
11637
Oid partConstrRelid;
11655
11638
bool parentConstrIsEnforced;
11639
+ bool partConstrIsEnforced;
11640
+ bool partConstrParentIsSet;
11656
11641
11657
11642
/* Fetch the parent constraint tuple */
11658
11643
parentConstrTup = SearchSysCache1(CONSTROID,
@@ -11668,13 +11653,47 @@ AttachPartitionForeignKey(List **wqueue,
11668
11653
if (!HeapTupleIsValid(partcontup))
11669
11654
elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
11670
11655
partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11656
+ partConstrIsEnforced = partConstr->conenforced;
11671
11657
partConstrFrelid = partConstr->confrelid;
11672
11658
partConstrRelid = partConstr->conrelid;
11673
11659
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
+
11674
11686
/*
11675
11687
* If the referenced table is partitioned, then the partition we're
11676
11688
* attaching now has extra pg_constraint rows and action triggers that are
11677
11689
* 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.
11678
11697
*/
11679
11698
if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
11680
11699
{
@@ -11693,6 +11712,53 @@ AttachPartitionForeignKey(List **wqueue,
11693
11712
*/
11694
11713
queueValidation = parentConstr->convalidated && !partConstr->convalidated;
11695
11714
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
+
11696
11762
ReleaseSysCache(partcontup);
11697
11763
ReleaseSysCache(parentConstrTup);
11698
11764
@@ -11705,8 +11771,10 @@ AttachPartitionForeignKey(List **wqueue,
11705
11771
DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
11706
11772
partConstrRelid);
11707
11773
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));
11710
11778
11711
11779
/*
11712
11780
* Like the constraint, attach partition's "check" triggers to the
@@ -12306,6 +12374,17 @@ ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12306
12374
12307
12375
/* Drop all the triggers */
12308
12376
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);
12309
12388
}
12310
12389
else if (changed) /* Create triggers */
12311
12390
{
@@ -12631,13 +12710,40 @@ AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
12631
12710
true, NULL, 1, &pkey);
12632
12711
12633
12712
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);
12640
12729
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
+ }
12641
12747
systable_endscan(pscan);
12642
12748
}
12643
12749
@@ -20811,7 +20917,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
20811
20917
{
20812
20918
ForeignKeyCacheInfo *fk = lfirst(cell);
20813
20919
HeapTuple contup;
20920
+ HeapTuple parentContup;
20814
20921
Form_pg_constraint conform;
20922
+ Oid parentConstrIsEnforced;
20815
20923
20816
20924
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
20817
20925
if (!HeapTupleIsValid(contup))
@@ -20830,12 +20938,34 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
20830
20938
continue;
20831
20939
}
20832
20940
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
+
20833
20951
/*
20834
20952
* The constraint on this table must be marked no longer a child of
20835
20953
* the parent's constraint, as do its check triggers.
20836
20954
*/
20837
20955
ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
20838
20956
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
+
20839
20969
/*
20840
20970
* Also, look up the partition's "check" triggers corresponding to the
20841
20971
* ENFORCED constraint being detached and detach them from the parent
0 commit comments