@@ -574,8 +574,9 @@ static void createForeignKeyActionTriggers(Relation rel, Oid refRelOid,
574
574
Oid indexOid,
575
575
Oid parentDelTrigger, Oid parentUpdTrigger,
576
576
Oid *deleteTrigOid, Oid *updateTrigOid);
577
- static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
578
- Oid partRelid,
577
+ static bool tryAttachPartitionForeignKey(List **wqueue,
578
+ ForeignKeyCacheInfo *fk,
579
+ Relation partition,
579
580
Oid parentConstrOid, int numfks,
580
581
AttrNumber *mapped_conkey, AttrNumber *confkey,
581
582
Oid *conpfeqop,
@@ -9772,22 +9773,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9772
9773
* Validity checks (permission checks wait till we have the column
9773
9774
* numbers)
9774
9775
*/
9775
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9776
- {
9777
- if (!recurse)
9778
- ereport(ERROR,
9779
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9780
- errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
9781
- RelationGetRelationName(rel),
9782
- RelationGetRelationName(pkrel))));
9783
- if (fkconstraint->skip_validation && !fkconstraint->initially_valid)
9784
- ereport(ERROR,
9785
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9786
- errmsg("cannot add NOT VALID foreign key on partitioned table \"%s\" referencing relation \"%s\"",
9787
- RelationGetRelationName(rel),
9788
- RelationGetRelationName(pkrel)),
9789
- errdetail("This feature is not yet supported on partitioned tables.")));
9790
- }
9776
+ if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9777
+ ereport(ERROR,
9778
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9779
+ errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
9780
+ RelationGetRelationName(rel),
9781
+ RelationGetRelationName(pkrel))));
9791
9782
9792
9783
if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
9793
9784
pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
@@ -10782,8 +10773,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
10782
10773
*/
10783
10774
for (int i = 0; i < pd->nparts; i++)
10784
10775
{
10785
- Oid partitionId = pd->oids[i];
10786
- Relation partition = table_open(partitionId, lockmode);
10776
+ Relation partition = table_open(pd->oids[i], lockmode);
10787
10777
List *partFKs;
10788
10778
AttrMap *attmap;
10789
10779
AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
@@ -10807,8 +10797,9 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
10807
10797
ForeignKeyCacheInfo *fk;
10808
10798
10809
10799
fk = lfirst_node(ForeignKeyCacheInfo, cell);
10810
- if (tryAttachPartitionForeignKey(fk,
10811
- partitionId,
10800
+ if (tryAttachPartitionForeignKey(wqueue,
10801
+ fk,
10802
+ partition,
10812
10803
parentConstr,
10813
10804
numfks,
10814
10805
mapped_fkattnum,
@@ -11260,8 +11251,9 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
11260
11251
{
11261
11252
ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
11262
11253
11263
- if (tryAttachPartitionForeignKey(fk,
11264
- RelationGetRelid(partRel),
11254
+ if (tryAttachPartitionForeignKey(wqueue,
11255
+ fk,
11256
+ partRel,
11265
11257
parentConstrOid,
11266
11258
numfks,
11267
11259
mapped_conkey,
@@ -11364,8 +11356,9 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
11364
11356
* return false.
11365
11357
*/
11366
11358
static bool
11367
- tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11368
- Oid partRelid,
11359
+ tryAttachPartitionForeignKey(List **wqueue,
11360
+ ForeignKeyCacheInfo *fk,
11361
+ Relation partition,
11369
11362
Oid parentConstrOid,
11370
11363
int numfks,
11371
11364
AttrNumber *mapped_conkey,
@@ -11384,12 +11377,15 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11384
11377
HeapTuple trigtup;
11385
11378
Oid insertTriggerOid,
11386
11379
updateTriggerOid;
11380
+ bool parentConstrIsValid;
11381
+ bool partConstrIsValid;
11387
11382
11388
11383
parentConstrTup = SearchSysCache1(CONSTROID,
11389
11384
ObjectIdGetDatum(parentConstrOid));
11390
11385
if (!HeapTupleIsValid(parentConstrTup))
11391
11386
elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
11392
11387
parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11388
+ parentConstrIsValid = parentConstr->convalidated;
11393
11389
11394
11390
/*
11395
11391
* Do some quick & easy initial checks. If any of these fail, we cannot
@@ -11412,17 +11408,18 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11412
11408
}
11413
11409
11414
11410
/*
11415
- * Looks good so far; do some more extensive checks. Presumably the check
11416
- * for 'convalidated' could be dropped, since we don't really care about
11417
- * that, but let's be careful for now.
11411
+ * Looks good so far; perform more extensive checks except for
11412
+ * convalidated. There's no need to worry about it because attaching a
11413
+ * valid parent constraint to an invalid child constraint will eventually
11414
+ * trigger implicit data validation for the child.
11418
11415
*/
11419
11416
partcontup = SearchSysCache1(CONSTROID,
11420
11417
ObjectIdGetDatum(fk->conoid));
11421
11418
if (!HeapTupleIsValid(partcontup))
11422
11419
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
11423
11420
partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11421
+ partConstrIsValid = partConstr->convalidated;
11424
11422
if (OidIsValid(partConstr->conparentid) ||
11425
- !partConstr->convalidated ||
11426
11423
partConstr->condeferrable != parentConstr->condeferrable ||
11427
11424
partConstr->condeferred != parentConstr->condeferred ||
11428
11425
partConstr->confupdtype != parentConstr->confupdtype ||
@@ -11481,7 +11478,8 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11481
11478
11482
11479
systable_endscan(scan);
11483
11480
11484
- ConstraintSetParentConstraint(fk->conoid, parentConstrOid, partRelid);
11481
+ ConstraintSetParentConstraint(fk->conoid, parentConstrOid,
11482
+ RelationGetRelid(partition));
11485
11483
11486
11484
/*
11487
11485
* Like the constraint, attach partition's "check" triggers to the
@@ -11492,10 +11490,10 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11492
11490
&insertTriggerOid, &updateTriggerOid);
11493
11491
Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
11494
11492
TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
11495
- partRelid );
11493
+ RelationGetRelid(partition) );
11496
11494
Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
11497
11495
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
11498
- partRelid );
11496
+ RelationGetRelid(partition) );
11499
11497
11500
11498
/*
11501
11499
* If the referenced table is partitioned, then the partition we're
@@ -11573,6 +11571,32 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11573
11571
}
11574
11572
11575
11573
CommandCounterIncrement();
11574
+
11575
+ /*
11576
+ * If a valid parent constraint is attached to a NOT VALID child
11577
+ * constraint, implicitly trigger validation for the child constraint.
11578
+ * This behavior aligns with creating a new constraint on the child table
11579
+ * rather than attaching it to the existing one, as it would ultimately
11580
+ * validate the child data. Conversely, having an invalid parent
11581
+ * constraint while the child constraint is valid doesn't cause any harm.
11582
+ */
11583
+ if (parentConstrIsValid && !partConstrIsValid)
11584
+ {
11585
+ Relation conrel;
11586
+
11587
+ conrel = table_open(ConstraintRelationId, RowExclusiveLock);
11588
+
11589
+ partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
11590
+ if (!HeapTupleIsValid(partcontup))
11591
+ elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
11592
+
11593
+ /* Using the same lock as used in AT_ValidateConstraint */
11594
+ QueueFKConstraintValidation(wqueue, conrel, partition, partcontup,
11595
+ ShareUpdateExclusiveLock);
11596
+ ReleaseSysCache(partcontup);
11597
+ table_close(conrel, RowExclusiveLock);
11598
+ }
11599
+
11576
11600
return true;
11577
11601
}
11578
11602
@@ -12113,7 +12137,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
12113
12137
*
12114
12138
* Add an entry to the wqueue to validate the given foreign key constraint in
12115
12139
* Phase 3 and update the convalidated field in the pg_constraint catalog
12116
- * for the specified relation.
12140
+ * for the specified relation and all its children .
12117
12141
*/
12118
12142
static void
12119
12143
QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
@@ -12126,6 +12150,7 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
12126
12150
12127
12151
con = (Form_pg_constraint) GETSTRUCT(contuple);
12128
12152
Assert(con->contype == CONSTRAINT_FOREIGN);
12153
+ Assert(!con->convalidated);
12129
12154
12130
12155
if (rel->rd_rel->relkind == RELKIND_RELATION)
12131
12156
{
@@ -12151,9 +12176,48 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
12151
12176
}
12152
12177
12153
12178
/*
12154
- * We disallow creating invalid foreign keys to or from partitioned
12155
- * tables, so ignoring the recursion bit is okay .
12179
+ * If the table at either end of the constraint is partitioned, we need to
12180
+ * recurse and handle every constraint that is a child of this constraint .
12156
12181
*/
12182
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12183
+ get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
12184
+ {
12185
+ ScanKeyData pkey;
12186
+ SysScanDesc pscan;
12187
+ HeapTuple childtup;
12188
+
12189
+ ScanKeyInit(&pkey,
12190
+ Anum_pg_constraint_conparentid,
12191
+ BTEqualStrategyNumber, F_OIDEQ,
12192
+ ObjectIdGetDatum(con->oid));
12193
+
12194
+ pscan = systable_beginscan(conrel, ConstraintParentIndexId,
12195
+ true, NULL, 1, &pkey);
12196
+
12197
+ while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
12198
+ {
12199
+ Form_pg_constraint childcon;
12200
+ Relation childrel;
12201
+
12202
+ childcon = (Form_pg_constraint) GETSTRUCT(childtup);
12203
+
12204
+ /*
12205
+ * If the child constraint has already been validated, no further
12206
+ * action is required for it or its descendants, as they are all
12207
+ * valid.
12208
+ */
12209
+ if (childcon->convalidated)
12210
+ continue;
12211
+
12212
+ childrel = table_open(childcon->conrelid, lockmode);
12213
+
12214
+ QueueFKConstraintValidation(wqueue, conrel, childrel, childtup,
12215
+ lockmode);
12216
+ table_close(childrel, NoLock);
12217
+ }
12218
+
12219
+ systable_endscan(pscan);
12220
+ }
12157
12221
12158
12222
/*
12159
12223
* Now update the catalog, while we have the door open.
0 commit comments