@@ -325,6 +325,9 @@ static void AlterSeqNamespaces(Relation classRel, Relation rel,
325
325
LOCKMODE lockmode);
326
326
static ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
327
327
bool recurse, bool recursing, LOCKMODE lockmode);
328
+ static bool ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
329
+ Relation rel, HeapTuple contuple, List **otherrelids,
330
+ LOCKMODE lockmode);
328
331
static ObjectAddress ATExecValidateConstraint(List **wqueue,
329
332
Relation rel, char *constrName,
330
333
bool recurse, bool recursing, LOCKMODE lockmode);
@@ -4212,6 +4215,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
4212
4215
break;
4213
4216
case AT_AlterConstraint: /* ALTER CONSTRAINT */
4214
4217
ATSimplePermissions(rel, ATT_TABLE);
4218
+ /* Recursion occurs during execution phase */
4215
4219
pass = AT_PASS_MISC;
4216
4220
break;
4217
4221
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
@@ -9268,28 +9272,29 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
9268
9272
* Update the attributes of a constraint.
9269
9273
*
9270
9274
* Currently only works for Foreign Key constraints.
9271
- * Foreign keys do not inherit, so we purposely ignore the
9272
- * recursion bit here, but we keep the API the same for when
9273
- * other constraint types are supported.
9274
9275
*
9275
9276
* If the constraint is modified, returns its address; otherwise, return
9276
9277
* InvalidObjectAddress.
9277
9278
*/
9278
9279
static ObjectAddress
9279
- ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9280
- bool recurse, bool recursing, LOCKMODE lockmode)
9280
+ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, bool recurse,
9281
+ bool recursing, LOCKMODE lockmode)
9281
9282
{
9282
9283
Constraint *cmdcon;
9283
9284
Relation conrel;
9285
+ Relation tgrel;
9284
9286
SysScanDesc scan;
9285
9287
ScanKeyData skey[3];
9286
9288
HeapTuple contuple;
9287
9289
Form_pg_constraint currcon;
9288
9290
ObjectAddress address;
9291
+ List *otherrelids = NIL;
9292
+ ListCell *lc;
9289
9293
9290
9294
cmdcon = castNode(Constraint, cmd->def);
9291
9295
9292
9296
conrel = table_open(ConstraintRelationId, RowExclusiveLock);
9297
+ tgrel = table_open(TriggerRelationId, RowExclusiveLock);
9293
9298
9294
9299
/*
9295
9300
* Find and check the target constraint
@@ -9323,50 +9328,150 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9323
9328
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
9324
9329
cmdcon->conname, RelationGetRelationName(rel))));
9325
9330
9331
+ /*
9332
+ * If it's not the topmost constraint, raise an error.
9333
+ *
9334
+ * Altering a non-topmost constraint leaves some triggers untouched, since
9335
+ * they are not directly connected to this constraint; also, pg_dump would
9336
+ * ignore the deferrability status of the individual constraint, since it
9337
+ * only dumps topmost constraints. Avoid these problems by refusing this
9338
+ * operation and telling the user to alter the parent constraint instead.
9339
+ */
9340
+ if (OidIsValid(currcon->conparentid))
9341
+ {
9342
+ HeapTuple tp;
9343
+ Oid parent = currcon->conparentid;
9344
+ char *ancestorname = NULL;
9345
+ char *ancestortable = NULL;
9346
+
9347
+ /* Loop to find the topmost constraint */
9348
+ while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
9349
+ {
9350
+ Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
9351
+
9352
+ /* If no parent, this is the constraint we want */
9353
+ if (!OidIsValid(contup->conparentid))
9354
+ {
9355
+ ancestorname = pstrdup(NameStr(contup->conname));
9356
+ ancestortable = get_rel_name(contup->conrelid);
9357
+ ReleaseSysCache(tp);
9358
+ break;
9359
+ }
9360
+
9361
+ parent = contup->conparentid;
9362
+ ReleaseSysCache(tp);
9363
+ }
9364
+
9365
+ ereport(ERROR,
9366
+ (errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
9367
+ cmdcon->conname, RelationGetRelationName(rel)),
9368
+ ancestorname && ancestortable ?
9369
+ errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
9370
+ cmdcon->conname, ancestorname, ancestortable) : 0,
9371
+ errhint("You may alter the constraint it derives from, instead.")));
9372
+ }
9373
+
9374
+ /*
9375
+ * Do the actual catalog work. We can skip changing if already in the
9376
+ * desired state, but not if a partitioned table: partitions need to be
9377
+ * processed regardless, in case they had the constraint locally changed.
9378
+ */
9379
+ address = InvalidObjectAddress;
9380
+ if (currcon->condeferrable != cmdcon->deferrable ||
9381
+ currcon->condeferred != cmdcon->initdeferred ||
9382
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9383
+ {
9384
+ if (ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, rel, contuple,
9385
+ &otherrelids, lockmode))
9386
+ ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
9387
+ }
9388
+
9389
+ /*
9390
+ * ATExecConstrRecurse already invalidated relcache for the relations
9391
+ * having the constraint itself; here we also invalidate for relations
9392
+ * that have any triggers that are part of the constraint.
9393
+ */
9394
+ foreach(lc, otherrelids)
9395
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
9396
+
9397
+ systable_endscan(scan);
9398
+
9399
+ table_close(tgrel, RowExclusiveLock);
9400
+ table_close(conrel, RowExclusiveLock);
9401
+
9402
+ return address;
9403
+ }
9404
+
9405
+ /*
9406
+ * Recursive subroutine of ATExecAlterConstraint. Returns true if the
9407
+ * constraint is altered.
9408
+ *
9409
+ * *otherrelids is appended OIDs of relations containing affected triggers.
9410
+ *
9411
+ * Note that we must recurse even when the values are correct, in case
9412
+ * indirect descendants have had their constraints altered locally.
9413
+ * (This could be avoided if we forbade altering constraints in partitions
9414
+ * but existing releases don't do that.)
9415
+ */
9416
+ static bool
9417
+ ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
9418
+ Relation rel, HeapTuple contuple, List **otherrelids,
9419
+ LOCKMODE lockmode)
9420
+ {
9421
+ Form_pg_constraint currcon;
9422
+ Oid conoid;
9423
+ Oid refrelid;
9424
+ bool changed = false;
9425
+
9426
+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
9427
+ conoid = currcon->oid;
9428
+ refrelid = currcon->confrelid;
9429
+
9430
+ /*
9431
+ * Update pg_constraint with the flags from cmdcon.
9432
+ *
9433
+ * If called to modify a constraint that's already in the desired state,
9434
+ * silently do nothing.
9435
+ */
9326
9436
if (currcon->condeferrable != cmdcon->deferrable ||
9327
9437
currcon->condeferred != cmdcon->initdeferred)
9328
9438
{
9329
9439
HeapTuple copyTuple;
9330
- HeapTuple tgtuple;
9331
9440
Form_pg_constraint copy_con;
9332
- List *otherrelids = NIL ;
9441
+ HeapTuple tgtuple ;
9333
9442
ScanKeyData tgkey;
9334
9443
SysScanDesc tgscan;
9335
- Relation tgrel;
9336
- ListCell *lc;
9337
9444
9338
- /*
9339
- * Now update the catalog, while we have the door open.
9340
- */
9341
9445
copyTuple = heap_copytuple(contuple);
9342
9446
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
9343
9447
copy_con->condeferrable = cmdcon->deferrable;
9344
9448
copy_con->condeferred = cmdcon->initdeferred;
9345
9449
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
9346
9450
9347
9451
InvokeObjectPostAlterHook(ConstraintRelationId,
9348
- currcon->oid , 0);
9452
+ conoid , 0);
9349
9453
9350
9454
heap_freetuple(copyTuple);
9455
+ changed = true;
9456
+
9457
+ /* Make new constraint flags visible to others */
9458
+ CacheInvalidateRelcache(rel);
9351
9459
9352
9460
/*
9353
9461
* Now we need to update the multiple entries in pg_trigger that
9354
9462
* implement the constraint.
9355
9463
*/
9356
- tgrel = table_open(TriggerRelationId, RowExclusiveLock);
9357
-
9358
9464
ScanKeyInit(&tgkey,
9359
9465
Anum_pg_trigger_tgconstraint,
9360
9466
BTEqualStrategyNumber, F_OIDEQ,
9361
- ObjectIdGetDatum(currcon->oid));
9362
-
9467
+ ObjectIdGetDatum(conoid));
9363
9468
tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
9364
9469
NULL, 1, &tgkey);
9365
-
9366
9470
while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
9367
9471
{
9368
9472
Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
9369
9473
Form_pg_trigger copy_tg;
9474
+ HeapTuple copyTuple;
9370
9475
9371
9476
/*
9372
9477
* Remember OIDs of other relation(s) involved in FK constraint.
@@ -9375,8 +9480,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9375
9480
* change, but let's be conservative.)
9376
9481
*/
9377
9482
if (tgform->tgrelid != RelationGetRelid(rel))
9378
- otherrelids = list_append_unique_oid(otherrelids,
9379
- tgform->tgrelid);
9483
+ * otherrelids = list_append_unique_oid(* otherrelids,
9484
+ tgform->tgrelid);
9380
9485
9381
9486
/*
9382
9487
* Update deferrability of RI_FKey_noaction_del,
@@ -9403,31 +9508,46 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
9403
9508
}
9404
9509
9405
9510
systable_endscan(tgscan);
9511
+ }
9406
9512
9407
- table_close(tgrel, RowExclusiveLock);
9513
+ /*
9514
+ * If the table at either end of the constraint is partitioned, we need to
9515
+ * recurse and handle every constraint that is a child of this one.
9516
+ *
9517
+ * (This assumes that the recurse flag is forcibly set for partitioned
9518
+ * tables, and not set for legacy inheritance, though we don't check for
9519
+ * that here.)
9520
+ */
9521
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
9522
+ get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE)
9523
+ {
9524
+ ScanKeyData pkey;
9525
+ SysScanDesc pscan;
9526
+ HeapTuple childtup;
9408
9527
9409
- /*
9410
- * Invalidate relcache so that others see the new attributes. We must
9411
- * inval both the named rel and any others having relevant triggers.
9412
- * (At present there should always be exactly one other rel, but
9413
- * there's no need to hard-wire such an assumption here.)
9414
- */
9415
- CacheInvalidateRelcache(rel);
9416
- foreach(lc, otherrelids)
9528
+ ScanKeyInit(&pkey,
9529
+ Anum_pg_constraint_conparentid,
9530
+ BTEqualStrategyNumber, F_OIDEQ,
9531
+ ObjectIdGetDatum(conoid));
9532
+
9533
+ pscan = systable_beginscan(conrel, ConstraintParentIndexId,
9534
+ true, NULL, 1, &pkey);
9535
+
9536
+ while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
9417
9537
{
9418
- CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
9538
+ Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
9539
+ Relation childrel;
9540
+
9541
+ childrel = table_open(childcon->conrelid, lockmode);
9542
+ ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, childrel, childtup,
9543
+ otherrelids, lockmode);
9544
+ table_close(childrel, NoLock);
9419
9545
}
9420
9546
9421
- ObjectAddressSet(address, ConstraintRelationId, currcon->oid );
9547
+ systable_endscan(pscan );
9422
9548
}
9423
- else
9424
- address = InvalidObjectAddress;
9425
9549
9426
- systable_endscan(scan);
9427
-
9428
- table_close(conrel, RowExclusiveLock);
9429
-
9430
- return address;
9550
+ return changed;
9431
9551
}
9432
9552
9433
9553
/*
0 commit comments