@@ -322,6 +322,9 @@ static void AlterSeqNamespaces(Relation classRel, Relation rel,
322
322
LOCKMODE lockmode );
323
323
static ObjectAddress ATExecAlterConstraint (Relation rel , AlterTableCmd * cmd ,
324
324
bool recurse , bool recursing , LOCKMODE lockmode );
325
+ static bool ATExecAlterConstrRecurse (Constraint * cmdcon , Relation conrel ,
326
+ Relation tgrel , Relation rel , HeapTuple contuple ,
327
+ List * * otherrelids , LOCKMODE lockmode );
325
328
static ObjectAddress ATExecValidateConstraint (List * * wqueue , Relation rel ,
326
329
char * constrName , bool recurse , bool recursing ,
327
330
LOCKMODE lockmode );
@@ -3953,6 +3956,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
3953
3956
break ;
3954
3957
case AT_AlterConstraint : /* ALTER CONSTRAINT */
3955
3958
ATSimplePermissions (rel , ATT_TABLE );
3959
+ /* Recursion occurs during execution phase */
3956
3960
pass = AT_PASS_MISC ;
3957
3961
break ;
3958
3962
case AT_ValidateConstraint : /* VALIDATE CONSTRAINT */
@@ -8311,28 +8315,29 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel,
8311
8315
* Update the attributes of a constraint.
8312
8316
*
8313
8317
* Currently only works for Foreign Key constraints.
8314
- * Foreign keys do not inherit, so we purposely ignore the
8315
- * recursion bit here, but we keep the API the same for when
8316
- * other constraint types are supported.
8317
8318
*
8318
8319
* If the constraint is modified, returns its address; otherwise, return
8319
8320
* InvalidObjectAddress.
8320
8321
*/
8321
8322
static ObjectAddress
8322
- ATExecAlterConstraint (Relation rel , AlterTableCmd * cmd ,
8323
- bool recurse , bool recursing , LOCKMODE lockmode )
8323
+ ATExecAlterConstraint (Relation rel , AlterTableCmd * cmd , bool recurse ,
8324
+ bool recursing , LOCKMODE lockmode )
8324
8325
{
8325
8326
Constraint * cmdcon ;
8326
8327
Relation conrel ;
8328
+ Relation tgrel ;
8327
8329
SysScanDesc scan ;
8328
8330
ScanKeyData skey [3 ];
8329
8331
HeapTuple contuple ;
8330
8332
Form_pg_constraint currcon ;
8331
8333
ObjectAddress address ;
8334
+ List * otherrelids = NIL ;
8335
+ ListCell * lc ;
8332
8336
8333
8337
cmdcon = castNode (Constraint , cmd -> def );
8334
8338
8335
8339
conrel = heap_open (ConstraintRelationId , RowExclusiveLock );
8340
+ tgrel = heap_open (TriggerRelationId , RowExclusiveLock );
8336
8341
8337
8342
/*
8338
8343
* Find and check the target constraint
@@ -8366,17 +8371,118 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
8366
8371
errmsg ("constraint \"%s\" of relation \"%s\" is not a foreign key constraint" ,
8367
8372
cmdcon -> conname , RelationGetRelationName (rel ))));
8368
8373
8374
+ /*
8375
+ * If it's not the topmost constraint, raise an error.
8376
+ *
8377
+ * Altering a non-topmost constraint leaves some triggers untouched, since
8378
+ * they are not directly connected to this constraint; also, pg_dump would
8379
+ * ignore the deferrability status of the individual constraint, since it
8380
+ * only dumps topmost constraints. Avoid these problems by refusing this
8381
+ * operation and telling the user to alter the parent constraint instead.
8382
+ */
8383
+ if (OidIsValid (currcon -> conparentid ))
8384
+ {
8385
+ HeapTuple tp ;
8386
+ Oid parent = currcon -> conparentid ;
8387
+ char * ancestorname = NULL ;
8388
+ char * ancestortable = NULL ;
8389
+
8390
+ /* Loop to find the topmost constraint */
8391
+ while (HeapTupleIsValid (tp = SearchSysCache1 (CONSTROID , ObjectIdGetDatum (parent ))))
8392
+ {
8393
+ Form_pg_constraint contup = (Form_pg_constraint ) GETSTRUCT (tp );
8394
+
8395
+ /* If no parent, this is the constraint we want */
8396
+ if (!OidIsValid (contup -> conparentid ))
8397
+ {
8398
+ ancestorname = pstrdup (NameStr (contup -> conname ));
8399
+ ancestortable = get_rel_name (contup -> conrelid );
8400
+ ReleaseSysCache (tp );
8401
+ break ;
8402
+ }
8403
+
8404
+ parent = contup -> conparentid ;
8405
+ ReleaseSysCache (tp );
8406
+ }
8407
+
8408
+ ereport (ERROR ,
8409
+ (errmsg ("cannot alter constraint \"%s\" on relation \"%s\"" ,
8410
+ cmdcon -> conname , RelationGetRelationName (rel )),
8411
+ ancestorname && ancestortable ?
8412
+ errdetail ("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\"." ,
8413
+ cmdcon -> conname , ancestorname , ancestortable ) : 0 ,
8414
+ errhint ("You may alter the constraint it derives from, instead." )));
8415
+ }
8416
+
8417
+ /*
8418
+ * Do the actual catalog work. We can skip changing if already in the
8419
+ * desired state, but not if a partitioned table: partitions need to be
8420
+ * processed regardless, in case they've had it locally changed.
8421
+ */
8422
+ address = InvalidObjectAddress ;
8423
+ if (currcon -> condeferrable != cmdcon -> deferrable ||
8424
+ currcon -> condeferred != cmdcon -> initdeferred ||
8425
+ rel -> rd_rel -> relkind == RELKIND_PARTITIONED_TABLE )
8426
+ {
8427
+ if (ATExecAlterConstrRecurse (cmdcon , conrel , tgrel , rel , contuple ,
8428
+ & otherrelids , lockmode ))
8429
+ ObjectAddressSet (address , ConstraintRelationId ,
8430
+ HeapTupleGetOid (contuple ));
8431
+ }
8432
+
8433
+ /*
8434
+ * ATExecConstrRecurse already invalidated relcache for the relations
8435
+ * having the constraint itself; here we also invalidate for relations
8436
+ * that have any triggers that implement the constraint.
8437
+ */
8438
+ foreach (lc , otherrelids )
8439
+ CacheInvalidateRelcacheByRelid (lfirst_oid (lc ));
8440
+
8441
+ systable_endscan (scan );
8442
+
8443
+ heap_close (conrel , RowExclusiveLock );
8444
+ heap_close (tgrel , RowExclusiveLock );
8445
+
8446
+ return address ;
8447
+ }
8448
+
8449
+ /*
8450
+ * Recursive subroutine of ATExecAlterConstraint. Returns true if the
8451
+ * constraint is altered.
8452
+ *
8453
+ * *otherrelids is appended OIDs of relations containing affected triggers.
8454
+ *
8455
+ * Note that we must recurse even when the values are correct, in case
8456
+ * indirect descendants have had their constraints altered locally.
8457
+ * (This could be avoided if we forbade altering constraints in partitions
8458
+ * but existing releases don't do that.)
8459
+ */
8460
+ static bool
8461
+ ATExecAlterConstrRecurse (Constraint * cmdcon , Relation conrel , Relation tgrel ,
8462
+ Relation rel , HeapTuple contuple , List * * otherrelids ,
8463
+ LOCKMODE lockmode )
8464
+ {
8465
+ Form_pg_constraint currcon ;
8466
+ Oid conoid ;
8467
+ bool changed = false;
8468
+
8469
+ currcon = (Form_pg_constraint ) GETSTRUCT (contuple );
8470
+ conoid = HeapTupleGetOid (contuple );
8471
+
8472
+ /*
8473
+ * Update pg_constraint with the flags from cmdcon.
8474
+ *
8475
+ * If called to modify a constraint that's already in the desired state,
8476
+ * silently do nothing.
8477
+ */
8369
8478
if (currcon -> condeferrable != cmdcon -> deferrable ||
8370
8479
currcon -> condeferred != cmdcon -> initdeferred )
8371
8480
{
8372
8481
HeapTuple copyTuple ;
8373
- HeapTuple tgtuple ;
8374
8482
Form_pg_constraint copy_con ;
8375
- List * otherrelids = NIL ;
8483
+ HeapTuple tgtuple ;
8376
8484
ScanKeyData tgkey ;
8377
8485
SysScanDesc tgscan ;
8378
- Relation tgrel ;
8379
- ListCell * lc ;
8380
8486
8381
8487
/*
8382
8488
* Now update the catalog, while we have the door open.
@@ -8391,25 +8497,26 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
8391
8497
HeapTupleGetOid (contuple ), 0 );
8392
8498
8393
8499
heap_freetuple (copyTuple );
8500
+ changed = true;
8501
+
8502
+ /* Make new constraint flags visible to others */
8503
+ CacheInvalidateRelcache (rel );
8394
8504
8395
8505
/*
8396
8506
* Now we need to update the multiple entries in pg_trigger that
8397
8507
* implement the constraint.
8398
8508
*/
8399
- tgrel = heap_open (TriggerRelationId , RowExclusiveLock );
8400
-
8401
8509
ScanKeyInit (& tgkey ,
8402
8510
Anum_pg_trigger_tgconstraint ,
8403
8511
BTEqualStrategyNumber , F_OIDEQ ,
8404
8512
ObjectIdGetDatum (HeapTupleGetOid (contuple )));
8405
-
8406
8513
tgscan = systable_beginscan (tgrel , TriggerConstraintIndexId , true,
8407
8514
NULL , 1 , & tgkey );
8408
-
8409
8515
while (HeapTupleIsValid (tgtuple = systable_getnext (tgscan )))
8410
8516
{
8411
8517
Form_pg_trigger tgform = (Form_pg_trigger ) GETSTRUCT (tgtuple );
8412
8518
Form_pg_trigger copy_tg ;
8519
+ HeapTuple copyTuple ;
8413
8520
8414
8521
/*
8415
8522
* Remember OIDs of other relation(s) involved in FK constraint.
@@ -8418,8 +8525,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
8418
8525
* change, but let's be conservative.)
8419
8526
*/
8420
8527
if (tgform -> tgrelid != RelationGetRelid (rel ))
8421
- otherrelids = list_append_unique_oid (otherrelids ,
8422
- tgform -> tgrelid );
8528
+ * otherrelids = list_append_unique_oid (* otherrelids ,
8529
+ tgform -> tgrelid );
8423
8530
8424
8531
/*
8425
8532
* Update deferrability of RI_FKey_noaction_del,
@@ -8447,32 +8554,45 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
8447
8554
}
8448
8555
8449
8556
systable_endscan (tgscan );
8557
+ }
8450
8558
8451
- heap_close (tgrel , RowExclusiveLock );
8559
+ /*
8560
+ * If the referencing table is partitioned, we need to recurse and handle
8561
+ * every constraint that is a child of this one.
8562
+ *
8563
+ * (This assumes that the recurse flag is forcibly set for partitioned
8564
+ * tables, and not set for legacy inheritance, though we don't check for
8565
+ * that here.)
8566
+ */
8567
+ if (rel -> rd_rel -> relkind == RELKIND_PARTITIONED_TABLE )
8568
+ {
8569
+ ScanKeyData pkey ;
8570
+ SysScanDesc pscan ;
8571
+ HeapTuple childtup ;
8452
8572
8453
- /*
8454
- * Invalidate relcache so that others see the new attributes. We must
8455
- * inval both the named rel and any others having relevant triggers.
8456
- * (At present there should always be exactly one other rel, but
8457
- * there's no need to hard-wire such an assumption here.)
8458
- */
8459
- CacheInvalidateRelcache (rel );
8460
- foreach (lc , otherrelids )
8573
+ ScanKeyInit (& pkey ,
8574
+ Anum_pg_constraint_conparentid ,
8575
+ BTEqualStrategyNumber , F_OIDEQ ,
8576
+ ObjectIdGetDatum (conoid ));
8577
+
8578
+ pscan = systable_beginscan (conrel , ConstraintParentIndexId ,
8579
+ true, NULL , 1 , & pkey );
8580
+
8581
+ while (HeapTupleIsValid (childtup = systable_getnext (pscan )))
8461
8582
{
8462
- CacheInvalidateRelcacheByRelid (lfirst_oid (lc ));
8583
+ Form_pg_constraint childcon = (Form_pg_constraint ) GETSTRUCT (childtup );
8584
+ Relation childrel ;
8585
+
8586
+ childrel = heap_open (childcon -> conrelid , lockmode );
8587
+ ATExecAlterConstrRecurse (cmdcon , conrel , tgrel , childrel , childtup ,
8588
+ otherrelids , lockmode );
8589
+ heap_close (childrel , NoLock );
8463
8590
}
8464
8591
8465
- ObjectAddressSet (address , ConstraintRelationId ,
8466
- HeapTupleGetOid (contuple ));
8592
+ systable_endscan (pscan );
8467
8593
}
8468
- else
8469
- address = InvalidObjectAddress ;
8470
8594
8471
- systable_endscan (scan );
8472
-
8473
- heap_close (conrel , RowExclusiveLock );
8474
-
8475
- return address ;
8595
+ return changed ;
8476
8596
}
8477
8597
8478
8598
/*
0 commit comments