@@ -574,8 +574,9 @@ static void createForeignKeyActionTriggers(Relation rel, Oid refRelOid,
574574 Oid indexOid,
575575 Oid parentDelTrigger, Oid parentUpdTrigger,
576576 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,
579580 Oid parentConstrOid, int numfks,
580581 AttrNumber *mapped_conkey, AttrNumber *confkey,
581582 Oid *conpfeqop,
@@ -9772,22 +9773,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
97729773 * Validity checks (permission checks wait till we have the column
97739774 * numbers)
97749775 */
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)));
97919782
97929783 if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
97939784 pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
@@ -10782,14 +10773,12 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
1078210773 */
1078310774 for (int i = 0; i < pd->nparts; i++)
1078410775 {
10785- Oid partitionId = pd->oids[i];
10786- Relation partition = table_open(partitionId, lockmode);
10776+ Relation partition = table_open(pd->oids[i], lockmode);
1078710777 List *partFKs;
1078810778 AttrMap *attmap;
1078910779 AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
1079010780 bool attached;
1079110781 ObjectAddress address;
10792- ListCell *cell;
1079310782
1079410783 CheckAlterTableIsSafe(partition);
1079510784
@@ -10802,13 +10791,11 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
1080210791 /* Check whether an existing constraint can be repurposed */
1080310792 partFKs = copyObject(RelationGetFKeyList(partition));
1080410793 attached = false;
10805- foreach(cell , partFKs)
10794+ foreach_node(ForeignKeyCacheInfo, fk , partFKs)
1080610795 {
10807- ForeignKeyCacheInfo *fk;
10808-
10809- fk = lfirst_node(ForeignKeyCacheInfo, cell);
10810- if (tryAttachPartitionForeignKey(fk,
10811- partitionId,
10796+ if (tryAttachPartitionForeignKey(wqueue,
10797+ fk,
10798+ partition,
1081210799 parentConstr,
1081310800 numfks,
1081410801 mapped_fkattnum,
@@ -11260,8 +11247,9 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
1126011247 {
1126111248 ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
1126211249
11263- if (tryAttachPartitionForeignKey(fk,
11264- RelationGetRelid(partRel),
11250+ if (tryAttachPartitionForeignKey(wqueue,
11251+ fk,
11252+ partRel,
1126511253 parentConstrOid,
1126611254 numfks,
1126711255 mapped_conkey,
@@ -11364,8 +11352,9 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
1136411352 * return false.
1136511353 */
1136611354static bool
11367- tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11368- Oid partRelid,
11355+ tryAttachPartitionForeignKey(List **wqueue,
11356+ ForeignKeyCacheInfo *fk,
11357+ Relation partition,
1136911358 Oid parentConstrOid,
1137011359 int numfks,
1137111360 AttrNumber *mapped_conkey,
@@ -11379,6 +11368,7 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
1137911368 Form_pg_constraint parentConstr;
1138011369 HeapTuple partcontup;
1138111370 Form_pg_constraint partConstr;
11371+ bool queueValidation;
1138211372 ScanKeyData key;
1138311373 SysScanDesc scan;
1138411374 HeapTuple trigtup;
@@ -11411,18 +11401,12 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
1141111401 }
1141211402 }
1141311403
11414- /*
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.
11418- */
11419- partcontup = SearchSysCache1(CONSTROID,
11420- ObjectIdGetDatum(fk->conoid));
11404+ /* Looks good so far; perform more extensive checks. */
11405+ partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
1142111406 if (!HeapTupleIsValid(partcontup))
1142211407 elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
1142311408 partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
1142411409 if (OidIsValid(partConstr->conparentid) ||
11425- !partConstr->convalidated ||
1142611410 partConstr->condeferrable != parentConstr->condeferrable ||
1142711411 partConstr->condeferred != parentConstr->condeferred ||
1142811412 partConstr->confupdtype != parentConstr->confupdtype ||
@@ -11434,6 +11418,13 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
1143411418 return false;
1143511419 }
1143611420
11421+ /*
11422+ * Will we need to validate this constraint? A valid parent constraint
11423+ * implies that all child constraints have been validated, so if this one
11424+ * isn't, we must trigger phase 3 validation.
11425+ */
11426+ queueValidation = parentConstr->convalidated && !partConstr->convalidated;
11427+
1143711428 ReleaseSysCache(partcontup);
1143811429 ReleaseSysCache(parentConstrTup);
1143911430
@@ -11481,7 +11472,8 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
1148111472
1148211473 systable_endscan(scan);
1148311474
11484- ConstraintSetParentConstraint(fk->conoid, parentConstrOid, partRelid);
11475+ ConstraintSetParentConstraint(fk->conoid, parentConstrOid,
11476+ RelationGetRelid(partition));
1148511477
1148611478 /*
1148711479 * Like the constraint, attach partition's "check" triggers to the
@@ -11492,10 +11484,10 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
1149211484 &insertTriggerOid, &updateTriggerOid);
1149311485 Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
1149411486 TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
11495- partRelid );
11487+ RelationGetRelid(partition) );
1149611488 Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
1149711489 TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
11498- partRelid );
11490+ RelationGetRelid(partition) );
1149911491
1150011492 /*
1150111493 * If the referenced table is partitioned, then the partition we're
@@ -11572,7 +11564,33 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
1157211564 table_close(pg_constraint, RowShareLock);
1157311565 }
1157411566
11575- CommandCounterIncrement();
11567+ /* If validation is needed, put it in the queue now. */
11568+ if (queueValidation)
11569+ {
11570+ Relation conrel;
11571+
11572+ /*
11573+ * We updated this pg_constraint row above to set its parent;
11574+ * validating it will cause its convalidated flag to change, so we
11575+ * need CCI here. XXX it might work better to effect the convalidated
11576+ * changes for all constraints together during phase 3, but that
11577+ * requires more invasive code surgery.
11578+ */
11579+ CommandCounterIncrement();
11580+
11581+ conrel = table_open(ConstraintRelationId, RowExclusiveLock);
11582+
11583+ partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
11584+ if (!HeapTupleIsValid(partcontup))
11585+ elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
11586+
11587+ /* Use the same lock as for AT_ValidateConstraint */
11588+ QueueFKConstraintValidation(wqueue, conrel, partition, partcontup,
11589+ ShareUpdateExclusiveLock);
11590+ ReleaseSysCache(partcontup);
11591+ table_close(conrel, RowExclusiveLock);
11592+ }
11593+
1157611594 return true;
1157711595}
1157811596
@@ -12113,7 +12131,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
1211312131 *
1211412132 * Add an entry to the wqueue to validate the given foreign key constraint in
1211512133 * Phase 3 and update the convalidated field in the pg_constraint catalog
12116- * for the specified relation.
12134+ * for the specified relation and all its children .
1211712135 */
1211812136static void
1211912137QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
@@ -12126,6 +12144,7 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
1212612144
1212712145 con = (Form_pg_constraint) GETSTRUCT(contuple);
1212812146 Assert(con->contype == CONSTRAINT_FOREIGN);
12147+ Assert(!con->convalidated);
1212912148
1213012149 if (rel->rd_rel->relkind == RELKIND_RELATION)
1213112150 {
@@ -12151,9 +12170,48 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
1215112170 }
1215212171
1215312172 /*
12154- * We disallow creating invalid foreign keys to or from partitioned
12155- * tables, so ignoring the recursion bit is okay .
12173+ * If the table at either end of the constraint is partitioned, we need to
12174+ * recurse and handle every constraint that is a child of this constraint .
1215612175 */
12176+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12177+ get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
12178+ {
12179+ ScanKeyData pkey;
12180+ SysScanDesc pscan;
12181+ HeapTuple childtup;
12182+
12183+ ScanKeyInit(&pkey,
12184+ Anum_pg_constraint_conparentid,
12185+ BTEqualStrategyNumber, F_OIDEQ,
12186+ ObjectIdGetDatum(con->oid));
12187+
12188+ pscan = systable_beginscan(conrel, ConstraintParentIndexId,
12189+ true, NULL, 1, &pkey);
12190+
12191+ while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
12192+ {
12193+ Form_pg_constraint childcon;
12194+ Relation childrel;
12195+
12196+ childcon = (Form_pg_constraint) GETSTRUCT(childtup);
12197+
12198+ /*
12199+ * If the child constraint has already been validated, no further
12200+ * action is required for it or its descendants, as they are all
12201+ * valid.
12202+ */
12203+ if (childcon->convalidated)
12204+ continue;
12205+
12206+ childrel = table_open(childcon->conrelid, lockmode);
12207+
12208+ QueueFKConstraintValidation(wqueue, conrel, childrel, childtup,
12209+ lockmode);
12210+ table_close(childrel, NoLock);
12211+ }
12212+
12213+ systable_endscan(pscan);
12214+ }
1215712215
1215812216 /*
1215912217 * Now update the catalog, while we have the door open.
0 commit comments