@@ -357,6 +357,9 @@ static void AlterSeqNamespaces(Relation classRel, Relation rel,
357
357
LOCKMODE lockmode);
358
358
static ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
359
359
bool recurse, bool recursing, LOCKMODE lockmode);
360
+ static bool ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
361
+ Relation rel, HeapTuple contuple, List **otherrelids,
362
+ LOCKMODE lockmode);
360
363
static ObjectAddress ATExecValidateConstraint(List **wqueue,
361
364
Relation rel, char *constrName,
362
365
bool recurse, bool recursing, LOCKMODE lockmode);
@@ -4669,6 +4672,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
4669
4672
break;
4670
4673
case AT_AlterConstraint: /* ALTER CONSTRAINT */
4671
4674
ATSimplePermissions(rel, ATT_TABLE);
4675
+ /* Recursion occurs during execution phase */
4672
4676
pass = AT_PASS_MISC;
4673
4677
break;
4674
4678
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
@@ -10190,28 +10194,29 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
10190
10194
* Update the attributes of a constraint.
10191
10195
*
10192
10196
* Currently only works for Foreign Key constraints.
10193
- * Foreign keys do not inherit, so we purposely ignore the
10194
- * recursion bit here, but we keep the API the same for when
10195
- * other constraint types are supported.
10196
10197
*
10197
10198
* If the constraint is modified, returns its address; otherwise, return
10198
10199
* InvalidObjectAddress.
10199
10200
*/
10200
10201
static ObjectAddress
10201
- ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
10202
- bool recurse, bool recursing, LOCKMODE lockmode)
10202
+ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, bool recurse,
10203
+ bool recursing, LOCKMODE lockmode)
10203
10204
{
10204
10205
Constraint *cmdcon;
10205
10206
Relation conrel;
10207
+ Relation tgrel;
10206
10208
SysScanDesc scan;
10207
10209
ScanKeyData skey[3];
10208
10210
HeapTuple contuple;
10209
10211
Form_pg_constraint currcon;
10210
10212
ObjectAddress address;
10213
+ List *otherrelids = NIL;
10214
+ ListCell *lc;
10211
10215
10212
10216
cmdcon = castNode(Constraint, cmd->def);
10213
10217
10214
10218
conrel = table_open(ConstraintRelationId, RowExclusiveLock);
10219
+ tgrel = table_open(TriggerRelationId, RowExclusiveLock);
10215
10220
10216
10221
/*
10217
10222
* Find and check the target constraint
@@ -10245,50 +10250,150 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
10245
10250
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
10246
10251
cmdcon->conname, RelationGetRelationName(rel))));
10247
10252
10253
+ /*
10254
+ * If it's not the topmost constraint, raise an error.
10255
+ *
10256
+ * Altering a non-topmost constraint leaves some triggers untouched, since
10257
+ * they are not directly connected to this constraint; also, pg_dump would
10258
+ * ignore the deferrability status of the individual constraint, since it
10259
+ * only dumps topmost constraints. Avoid these problems by refusing this
10260
+ * operation and telling the user to alter the parent constraint instead.
10261
+ */
10262
+ if (OidIsValid(currcon->conparentid))
10263
+ {
10264
+ HeapTuple tp;
10265
+ Oid parent = currcon->conparentid;
10266
+ char *ancestorname = NULL;
10267
+ char *ancestortable = NULL;
10268
+
10269
+ /* Loop to find the topmost constraint */
10270
+ while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
10271
+ {
10272
+ Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
10273
+
10274
+ /* If no parent, this is the constraint we want */
10275
+ if (!OidIsValid(contup->conparentid))
10276
+ {
10277
+ ancestorname = pstrdup(NameStr(contup->conname));
10278
+ ancestortable = get_rel_name(contup->conrelid);
10279
+ ReleaseSysCache(tp);
10280
+ break;
10281
+ }
10282
+
10283
+ parent = contup->conparentid;
10284
+ ReleaseSysCache(tp);
10285
+ }
10286
+
10287
+ ereport(ERROR,
10288
+ (errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
10289
+ cmdcon->conname, RelationGetRelationName(rel)),
10290
+ ancestorname && ancestortable ?
10291
+ errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
10292
+ cmdcon->conname, ancestorname, ancestortable) : 0,
10293
+ errhint("You may alter the constraint it derives from, instead.")));
10294
+ }
10295
+
10296
+ /*
10297
+ * Do the actual catalog work. We can skip changing if already in the
10298
+ * desired state, but not if a partitioned table: partitions need to be
10299
+ * processed regardless, in case they had the constraint locally changed.
10300
+ */
10301
+ address = InvalidObjectAddress;
10302
+ if (currcon->condeferrable != cmdcon->deferrable ||
10303
+ currcon->condeferred != cmdcon->initdeferred ||
10304
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
10305
+ {
10306
+ if (ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, rel, contuple,
10307
+ &otherrelids, lockmode))
10308
+ ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
10309
+ }
10310
+
10311
+ /*
10312
+ * ATExecConstrRecurse already invalidated relcache for the relations
10313
+ * having the constraint itself; here we also invalidate for relations
10314
+ * that have any triggers that are part of the constraint.
10315
+ */
10316
+ foreach(lc, otherrelids)
10317
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
10318
+
10319
+ systable_endscan(scan);
10320
+
10321
+ table_close(tgrel, RowExclusiveLock);
10322
+ table_close(conrel, RowExclusiveLock);
10323
+
10324
+ return address;
10325
+ }
10326
+
10327
+ /*
10328
+ * Recursive subroutine of ATExecAlterConstraint. Returns true if the
10329
+ * constraint is altered.
10330
+ *
10331
+ * *otherrelids is appended OIDs of relations containing affected triggers.
10332
+ *
10333
+ * Note that we must recurse even when the values are correct, in case
10334
+ * indirect descendants have had their constraints altered locally.
10335
+ * (This could be avoided if we forbade altering constraints in partitions
10336
+ * but existing releases don't do that.)
10337
+ */
10338
+ static bool
10339
+ ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
10340
+ Relation rel, HeapTuple contuple, List **otherrelids,
10341
+ LOCKMODE lockmode)
10342
+ {
10343
+ Form_pg_constraint currcon;
10344
+ Oid conoid;
10345
+ Oid refrelid;
10346
+ bool changed = false;
10347
+
10348
+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
10349
+ conoid = currcon->oid;
10350
+ refrelid = currcon->confrelid;
10351
+
10352
+ /*
10353
+ * Update pg_constraint with the flags from cmdcon.
10354
+ *
10355
+ * If called to modify a constraint that's already in the desired state,
10356
+ * silently do nothing.
10357
+ */
10248
10358
if (currcon->condeferrable != cmdcon->deferrable ||
10249
10359
currcon->condeferred != cmdcon->initdeferred)
10250
10360
{
10251
10361
HeapTuple copyTuple;
10252
- HeapTuple tgtuple;
10253
10362
Form_pg_constraint copy_con;
10254
- List *otherrelids = NIL ;
10363
+ HeapTuple tgtuple ;
10255
10364
ScanKeyData tgkey;
10256
10365
SysScanDesc tgscan;
10257
- Relation tgrel;
10258
- ListCell *lc;
10259
10366
10260
- /*
10261
- * Now update the catalog, while we have the door open.
10262
- */
10263
10367
copyTuple = heap_copytuple(contuple);
10264
10368
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
10265
10369
copy_con->condeferrable = cmdcon->deferrable;
10266
10370
copy_con->condeferred = cmdcon->initdeferred;
10267
10371
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
10268
10372
10269
10373
InvokeObjectPostAlterHook(ConstraintRelationId,
10270
- currcon->oid , 0);
10374
+ conoid , 0);
10271
10375
10272
10376
heap_freetuple(copyTuple);
10377
+ changed = true;
10378
+
10379
+ /* Make new constraint flags visible to others */
10380
+ CacheInvalidateRelcache(rel);
10273
10381
10274
10382
/*
10275
10383
* Now we need to update the multiple entries in pg_trigger that
10276
10384
* implement the constraint.
10277
10385
*/
10278
- tgrel = table_open(TriggerRelationId, RowExclusiveLock);
10279
-
10280
10386
ScanKeyInit(&tgkey,
10281
10387
Anum_pg_trigger_tgconstraint,
10282
10388
BTEqualStrategyNumber, F_OIDEQ,
10283
- ObjectIdGetDatum(currcon->oid));
10284
-
10389
+ ObjectIdGetDatum(conoid));
10285
10390
tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
10286
10391
NULL, 1, &tgkey);
10287
-
10288
10392
while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
10289
10393
{
10290
10394
Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
10291
10395
Form_pg_trigger copy_tg;
10396
+ HeapTuple copyTuple;
10292
10397
10293
10398
/*
10294
10399
* Remember OIDs of other relation(s) involved in FK constraint.
@@ -10297,8 +10402,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
10297
10402
* change, but let's be conservative.)
10298
10403
*/
10299
10404
if (tgform->tgrelid != RelationGetRelid(rel))
10300
- otherrelids = list_append_unique_oid(otherrelids,
10301
- tgform->tgrelid);
10405
+ * otherrelids = list_append_unique_oid(* otherrelids,
10406
+ tgform->tgrelid);
10302
10407
10303
10408
/*
10304
10409
* Update deferrability of RI_FKey_noaction_del,
@@ -10325,31 +10430,46 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
10325
10430
}
10326
10431
10327
10432
systable_endscan(tgscan);
10433
+ }
10328
10434
10329
- table_close(tgrel, RowExclusiveLock);
10435
+ /*
10436
+ * If the table at either end of the constraint is partitioned, we need to
10437
+ * recurse and handle every constraint that is a child of this one.
10438
+ *
10439
+ * (This assumes that the recurse flag is forcibly set for partitioned
10440
+ * tables, and not set for legacy inheritance, though we don't check for
10441
+ * that here.)
10442
+ */
10443
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
10444
+ get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE)
10445
+ {
10446
+ ScanKeyData pkey;
10447
+ SysScanDesc pscan;
10448
+ HeapTuple childtup;
10330
10449
10331
- /*
10332
- * Invalidate relcache so that others see the new attributes. We must
10333
- * inval both the named rel and any others having relevant triggers.
10334
- * (At present there should always be exactly one other rel, but
10335
- * there's no need to hard-wire such an assumption here.)
10336
- */
10337
- CacheInvalidateRelcache(rel);
10338
- foreach(lc, otherrelids)
10450
+ ScanKeyInit(&pkey,
10451
+ Anum_pg_constraint_conparentid,
10452
+ BTEqualStrategyNumber, F_OIDEQ,
10453
+ ObjectIdGetDatum(conoid));
10454
+
10455
+ pscan = systable_beginscan(conrel, ConstraintParentIndexId,
10456
+ true, NULL, 1, &pkey);
10457
+
10458
+ while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
10339
10459
{
10340
- CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
10460
+ Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
10461
+ Relation childrel;
10462
+
10463
+ childrel = table_open(childcon->conrelid, lockmode);
10464
+ ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, childrel, childtup,
10465
+ otherrelids, lockmode);
10466
+ table_close(childrel, NoLock);
10341
10467
}
10342
10468
10343
- ObjectAddressSet(address, ConstraintRelationId, currcon->oid );
10469
+ systable_endscan(pscan );
10344
10470
}
10345
- else
10346
- address = InvalidObjectAddress;
10347
10471
10348
- systable_endscan(scan);
10349
-
10350
- table_close(conrel, RowExclusiveLock);
10351
-
10352
- return address;
10472
+ return changed;
10353
10473
}
10354
10474
10355
10475
/*
0 commit comments