@@ -328,6 +328,9 @@ static void AlterSeqNamespaces(Relation classRel, Relation rel,
328
328
LOCKMODE lockmode);
329
329
static ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
330
330
bool recurse, bool recursing, LOCKMODE lockmode);
331
+ static bool ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
332
+ Relation rel, HeapTuple contuple, List **otherrelids,
333
+ LOCKMODE lockmode);
331
334
static ObjectAddress ATExecValidateConstraint(List **wqueue,
332
335
Relation rel, char *constrName,
333
336
bool recurse, bool recursing, LOCKMODE lockmode);
@@ -4292,6 +4295,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
4292
4295
break;
4293
4296
case AT_AlterConstraint: /* ALTER CONSTRAINT */
4294
4297
ATSimplePermissions(rel, ATT_TABLE);
4298
+ /* Recursion occurs during execution phase */
4295
4299
pass = AT_PASS_MISC;
4296
4300
break;
4297
4301
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
@@ -9718,28 +9722,29 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
9718
9722
* Update the attributes of a constraint.
9719
9723
*
9720
9724
* Currently only works for Foreign Key constraints.
9721
- * Foreign keys do not inherit, so we purposely ignore the
9722
- * recursion bit here, but we keep the API the same for when
9723
- * other constraint types are supported.
9724
9725
*
9725
9726
* If the constraint is modified, returns its address; otherwise, return
9726
9727
* InvalidObjectAddress.
9727
9728
*/
9728
9729
static ObjectAddress
9729
- ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9730
- bool recurse, bool recursing, LOCKMODE lockmode)
9730
+ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, bool recurse,
9731
+ bool recursing, LOCKMODE lockmode)
9731
9732
{
9732
9733
Constraint *cmdcon;
9733
9734
Relation conrel;
9735
+ Relation tgrel;
9734
9736
SysScanDesc scan;
9735
9737
ScanKeyData skey[3];
9736
9738
HeapTuple contuple;
9737
9739
Form_pg_constraint currcon;
9738
9740
ObjectAddress address;
9741
+ List *otherrelids = NIL;
9742
+ ListCell *lc;
9739
9743
9740
9744
cmdcon = castNode(Constraint, cmd->def);
9741
9745
9742
9746
conrel = table_open(ConstraintRelationId, RowExclusiveLock);
9747
+ tgrel = table_open(TriggerRelationId, RowExclusiveLock);
9743
9748
9744
9749
/*
9745
9750
* Find and check the target constraint
@@ -9773,50 +9778,150 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9773
9778
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
9774
9779
cmdcon->conname, RelationGetRelationName(rel))));
9775
9780
9781
+ /*
9782
+ * If it's not the topmost constraint, raise an error.
9783
+ *
9784
+ * Altering a non-topmost constraint leaves some triggers untouched, since
9785
+ * they are not directly connected to this constraint; also, pg_dump would
9786
+ * ignore the deferrability status of the individual constraint, since it
9787
+ * only dumps topmost constraints. Avoid these problems by refusing this
9788
+ * operation and telling the user to alter the parent constraint instead.
9789
+ */
9790
+ if (OidIsValid(currcon->conparentid))
9791
+ {
9792
+ HeapTuple tp;
9793
+ Oid parent = currcon->conparentid;
9794
+ char *ancestorname = NULL;
9795
+ char *ancestortable = NULL;
9796
+
9797
+ /* Loop to find the topmost constraint */
9798
+ while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
9799
+ {
9800
+ Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
9801
+
9802
+ /* If no parent, this is the constraint we want */
9803
+ if (!OidIsValid(contup->conparentid))
9804
+ {
9805
+ ancestorname = pstrdup(NameStr(contup->conname));
9806
+ ancestortable = get_rel_name(contup->conrelid);
9807
+ ReleaseSysCache(tp);
9808
+ break;
9809
+ }
9810
+
9811
+ parent = contup->conparentid;
9812
+ ReleaseSysCache(tp);
9813
+ }
9814
+
9815
+ ereport(ERROR,
9816
+ (errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
9817
+ cmdcon->conname, RelationGetRelationName(rel)),
9818
+ ancestorname && ancestortable ?
9819
+ errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
9820
+ cmdcon->conname, ancestorname, ancestortable) : 0,
9821
+ errhint("You may alter the constraint it derives from, instead.")));
9822
+ }
9823
+
9824
+ /*
9825
+ * Do the actual catalog work. We can skip changing if already in the
9826
+ * desired state, but not if a partitioned table: partitions need to be
9827
+ * processed regardless, in case they had the constraint locally changed.
9828
+ */
9829
+ address = InvalidObjectAddress;
9830
+ if (currcon->condeferrable != cmdcon->deferrable ||
9831
+ currcon->condeferred != cmdcon->initdeferred ||
9832
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9833
+ {
9834
+ if (ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, rel, contuple,
9835
+ &otherrelids, lockmode))
9836
+ ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
9837
+ }
9838
+
9839
+ /*
9840
+ * ATExecConstrRecurse already invalidated relcache for the relations
9841
+ * having the constraint itself; here we also invalidate for relations
9842
+ * that have any triggers that are part of the constraint.
9843
+ */
9844
+ foreach(lc, otherrelids)
9845
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
9846
+
9847
+ systable_endscan(scan);
9848
+
9849
+ table_close(tgrel, RowExclusiveLock);
9850
+ table_close(conrel, RowExclusiveLock);
9851
+
9852
+ return address;
9853
+ }
9854
+
9855
+ /*
9856
+ * Recursive subroutine of ATExecAlterConstraint. Returns true if the
9857
+ * constraint is altered.
9858
+ *
9859
+ * *otherrelids is appended OIDs of relations containing affected triggers.
9860
+ *
9861
+ * Note that we must recurse even when the values are correct, in case
9862
+ * indirect descendants have had their constraints altered locally.
9863
+ * (This could be avoided if we forbade altering constraints in partitions
9864
+ * but existing releases don't do that.)
9865
+ */
9866
+ static bool
9867
+ ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
9868
+ Relation rel, HeapTuple contuple, List **otherrelids,
9869
+ LOCKMODE lockmode)
9870
+ {
9871
+ Form_pg_constraint currcon;
9872
+ Oid conoid;
9873
+ Oid refrelid;
9874
+ bool changed = false;
9875
+
9876
+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
9877
+ conoid = currcon->oid;
9878
+ refrelid = currcon->confrelid;
9879
+
9880
+ /*
9881
+ * Update pg_constraint with the flags from cmdcon.
9882
+ *
9883
+ * If called to modify a constraint that's already in the desired state,
9884
+ * silently do nothing.
9885
+ */
9776
9886
if (currcon->condeferrable != cmdcon->deferrable ||
9777
9887
currcon->condeferred != cmdcon->initdeferred)
9778
9888
{
9779
9889
HeapTuple copyTuple;
9780
- HeapTuple tgtuple;
9781
9890
Form_pg_constraint copy_con;
9782
- List *otherrelids = NIL ;
9891
+ HeapTuple tgtuple ;
9783
9892
ScanKeyData tgkey;
9784
9893
SysScanDesc tgscan;
9785
- Relation tgrel;
9786
- ListCell *lc;
9787
9894
9788
- /*
9789
- * Now update the catalog, while we have the door open.
9790
- */
9791
9895
copyTuple = heap_copytuple(contuple);
9792
9896
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
9793
9897
copy_con->condeferrable = cmdcon->deferrable;
9794
9898
copy_con->condeferred = cmdcon->initdeferred;
9795
9899
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
9796
9900
9797
9901
InvokeObjectPostAlterHook(ConstraintRelationId,
9798
- currcon->oid , 0);
9902
+ conoid , 0);
9799
9903
9800
9904
heap_freetuple(copyTuple);
9905
+ changed = true;
9906
+
9907
+ /* Make new constraint flags visible to others */
9908
+ CacheInvalidateRelcache(rel);
9801
9909
9802
9910
/*
9803
9911
* Now we need to update the multiple entries in pg_trigger that
9804
9912
* implement the constraint.
9805
9913
*/
9806
- tgrel = table_open(TriggerRelationId, RowExclusiveLock);
9807
-
9808
9914
ScanKeyInit(&tgkey,
9809
9915
Anum_pg_trigger_tgconstraint,
9810
9916
BTEqualStrategyNumber, F_OIDEQ,
9811
- ObjectIdGetDatum(currcon->oid));
9812
-
9917
+ ObjectIdGetDatum(conoid));
9813
9918
tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
9814
9919
NULL, 1, &tgkey);
9815
-
9816
9920
while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
9817
9921
{
9818
9922
Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
9819
9923
Form_pg_trigger copy_tg;
9924
+ HeapTuple copyTuple;
9820
9925
9821
9926
/*
9822
9927
* Remember OIDs of other relation(s) involved in FK constraint.
@@ -9825,8 +9930,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9825
9930
* change, but let's be conservative.)
9826
9931
*/
9827
9932
if (tgform->tgrelid != RelationGetRelid(rel))
9828
- otherrelids = list_append_unique_oid(otherrelids,
9829
- tgform->tgrelid);
9933
+ * otherrelids = list_append_unique_oid(* otherrelids,
9934
+ tgform->tgrelid);
9830
9935
9831
9936
/*
9832
9937
* Update deferrability of RI_FKey_noaction_del,
@@ -9853,31 +9958,46 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9853
9958
}
9854
9959
9855
9960
systable_endscan(tgscan);
9961
+ }
9856
9962
9857
- table_close(tgrel, RowExclusiveLock);
9963
+ /*
9964
+ * If the table at either end of the constraint is partitioned, we need to
9965
+ * recurse and handle every constraint that is a child of this one.
9966
+ *
9967
+ * (This assumes that the recurse flag is forcibly set for partitioned
9968
+ * tables, and not set for legacy inheritance, though we don't check for
9969
+ * that here.)
9970
+ */
9971
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
9972
+ get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE)
9973
+ {
9974
+ ScanKeyData pkey;
9975
+ SysScanDesc pscan;
9976
+ HeapTuple childtup;
9858
9977
9859
- /*
9860
- * Invalidate relcache so that others see the new attributes. We must
9861
- * inval both the named rel and any others having relevant triggers.
9862
- * (At present there should always be exactly one other rel, but
9863
- * there's no need to hard-wire such an assumption here.)
9864
- */
9865
- CacheInvalidateRelcache(rel);
9866
- foreach(lc, otherrelids)
9978
+ ScanKeyInit(&pkey,
9979
+ Anum_pg_constraint_conparentid,
9980
+ BTEqualStrategyNumber, F_OIDEQ,
9981
+ ObjectIdGetDatum(conoid));
9982
+
9983
+ pscan = systable_beginscan(conrel, ConstraintParentIndexId,
9984
+ true, NULL, 1, &pkey);
9985
+
9986
+ while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
9867
9987
{
9868
- CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
9988
+ Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
9989
+ Relation childrel;
9990
+
9991
+ childrel = table_open(childcon->conrelid, lockmode);
9992
+ ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, childrel, childtup,
9993
+ otherrelids, lockmode);
9994
+ table_close(childrel, NoLock);
9869
9995
}
9870
9996
9871
- ObjectAddressSet(address, ConstraintRelationId, currcon->oid );
9997
+ systable_endscan(pscan );
9872
9998
}
9873
- else
9874
- address = InvalidObjectAddress;
9875
9999
9876
- systable_endscan(scan);
9877
-
9878
- table_close(conrel, RowExclusiveLock);
9879
-
9880
- return address;
10000
+ return changed;
9881
10001
}
9882
10002
9883
10003
/*
0 commit comments