Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb663b94

Browse files
committed
Allow NOT VALID foreign key constraints on partitioned tables
This feature was intentionally omitted when FKs were first implementedfor partitioned tables, and had been requested a few times; theusefulness is clear.Validation can happen for each partition individually, which is usefulto contain the number of locks held and the duration; or it can beexecuted for the partitioning hierarchy as a single command, whichvalidates all child constraints that haven't been validated already.This is also useful to implement NOT ENFORCED constraints on top.Author: Amul Sul <sulamul@gmail.com>Discussion:https://postgr.es/m/CAAJ_b96Bp=-ZwihPPtuaNX=SrZ0U6ZsXD3+fgARO0JuKa8v2jQ@mail.gmail.com
1 parentb35434b commitb663b94

File tree

4 files changed

+263
-58
lines changed

4 files changed

+263
-58
lines changed

‎doc/src/sgml/ref/alter_table.sgml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,8 +486,6 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
486486
<para>
487487
Additional restrictions apply when unique or primary key constraints
488488
are added to partitioned tables; see <link linkend="sql-createtable"><command>CREATE TABLE</command></link>.
489-
Also, foreign key constraints on partitioned
490-
tables may not be declared <literal>NOT VALID</literal> at present.
491489
</para>
492490

493491
</listitem>

‎src/backend/commands/tablecmds.c

Lines changed: 104 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
OidpartitionId = pd->oids[i];
10786-
Relationpartition = table_open(partitionId, lockmode);
10776+
Relationpartition = table_open(pd->oids[i], lockmode);
1078710777
List *partFKs;
1078810778
AttrMap *attmap;
1078910779
AttrNumbermapped_fkattnum[INDEX_MAX_KEYS];
1079010780
boolattached;
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
*/
1136611354
static 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
HeapTuplepartcontup;
1138111370
Form_pg_constraint partConstr;
11371+
boolqueueValidation;
1138211372
ScanKeyData key;
1138311373
SysScanDesc scan;
1138411374
HeapTupletrigtup;
@@ -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+
Relationconrel;
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
*/
1211812136
static void
1211912137
QueueFKConstraintValidation(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 frompartitioned
12155-
*tables, so ignoring the recursion bit isokay.
12173+
*If the table at either end of the constraint ispartitioned, we need to
12174+
*recurse and handle every constraint that isa 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+
HeapTuplechildtup;
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+
Relationchildrel;
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.

‎src/test/regress/expected/foreign_key.out

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,12 +1597,6 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3
15971597
ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b)
15981598
REFERENCES fk_notpartitioned_pk;
15991599
ERROR: cannot use ONLY for foreign key on partitioned table "fk_partitioned_fk" referencing relation "fk_notpartitioned_pk"
1600-
-- Adding a NOT VALID foreign key on a partitioned table referencing
1601-
-- a non-partitioned table fails.
1602-
ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
1603-
REFERENCES fk_notpartitioned_pk NOT VALID;
1604-
ERROR: cannot add NOT VALID foreign key on partitioned table "fk_partitioned_fk" referencing relation "fk_notpartitioned_pk"
1605-
DETAIL: This feature is not yet supported on partitioned tables.
16061600
-- these inserts, targeting both the partition directly as well as the
16071601
-- partitioned table, should all fail
16081602
INSERT INTO fk_partitioned_fk (a,b) VALUES (500, 501);
@@ -1680,6 +1674,97 @@ DELETE FROM fk_notpartitioned_pk WHERE a = 1;
16801674
ERROR: update or delete on table "fk_notpartitioned_pk" violates foreign key constraint "fk_partitioned_fk_a_fkey" on table "fk_partitioned_fk"
16811675
DETAIL: Key (a)=(1) is still referenced from table "fk_partitioned_fk".
16821676
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
1677+
-- NOT VALID foreign keys on partitioned table
1678+
CREATE TABLE fk_notpartitioned_pk (a int, b int, PRIMARY KEY (a, b));
1679+
CREATE TABLE fk_partitioned_fk (b int, a int) PARTITION BY RANGE (a, b);
1680+
ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID;
1681+
-- Attaching a child table with the same valid foreign key constraint.
1682+
CREATE TABLE fk_partitioned_fk_1 (a int, b int);
1683+
ALTER TABLE fk_partitioned_fk_1 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk;
1684+
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FOR VALUES FROM (0,0) TO (1000,1000);
1685+
-- Child constraint will remain valid.
1686+
SELECT conname, convalidated, conrelid::regclass FROM pg_constraint
1687+
WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid;
1688+
conname | convalidated | conrelid
1689+
------------------------------+--------------+---------------------
1690+
fk_partitioned_fk_a_b_fkey | f | fk_partitioned_fk
1691+
fk_partitioned_fk_1_a_b_fkey | t | fk_partitioned_fk_1
1692+
(2 rows)
1693+
1694+
-- Validate the constraint
1695+
ALTER TABLE fk_partitioned_fk VALIDATE CONSTRAINT fk_partitioned_fk_a_b_fkey;
1696+
-- All constraints are now valid.
1697+
SELECT conname, convalidated, conrelid::regclass FROM pg_constraint
1698+
WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid;
1699+
conname | convalidated | conrelid
1700+
------------------------------+--------------+---------------------
1701+
fk_partitioned_fk_a_b_fkey | t | fk_partitioned_fk
1702+
fk_partitioned_fk_1_a_b_fkey | t | fk_partitioned_fk_1
1703+
(2 rows)
1704+
1705+
-- Attaching a child with a NOT VALID constraint.
1706+
CREATE TABLE fk_partitioned_fk_2 (a int, b int);
1707+
INSERT INTO fk_partitioned_fk_2 VALUES(1000, 1000); -- doesn't exist in referenced table
1708+
ALTER TABLE fk_partitioned_fk_2 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID;
1709+
-- It will fail because the attach operation implicitly validates the data.
1710+
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000);
1711+
ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_2_a_b_fkey"
1712+
DETAIL: Key (a, b)=(1000, 1000) is not present in table "fk_notpartitioned_pk".
1713+
-- Remove the invalid data and try again.
1714+
TRUNCATE fk_partitioned_fk_2;
1715+
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000);
1716+
-- The child constraint will also be valid.
1717+
SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_partitioned_fk_2'::regclass;
1718+
conname | convalidated
1719+
------------------------------+--------------
1720+
fk_partitioned_fk_2_a_b_fkey | t
1721+
(1 row)
1722+
1723+
-- Test case where the child constraint is invalid, the grandchild constraint
1724+
-- is valid, and the validation for the grandchild should be skipped when a
1725+
-- valid constraint is applied to the top parent.
1726+
CREATE TABLE fk_partitioned_fk_3 (a int, b int) PARTITION BY RANGE (a, b);
1727+
ALTER TABLE fk_partitioned_fk_3 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID;
1728+
CREATE TABLE fk_partitioned_fk_3_1 (a int, b int);
1729+
ALTER TABLE fk_partitioned_fk_3_1 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk;
1730+
ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_1 FOR VALUES FROM (2000,2000) TO (3000,3000);
1731+
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FOR VALUES FROM (2000,2000) TO (3000,3000);
1732+
-- All constraints are now valid.
1733+
SELECT conname, convalidated, conrelid::regclass FROM pg_constraint
1734+
WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid;
1735+
conname | convalidated | conrelid
1736+
--------------------------------+--------------+-----------------------
1737+
fk_partitioned_fk_a_b_fkey | t | fk_partitioned_fk
1738+
fk_partitioned_fk_1_a_b_fkey | t | fk_partitioned_fk_1
1739+
fk_partitioned_fk_2_a_b_fkey | t | fk_partitioned_fk_2
1740+
fk_partitioned_fk_3_a_b_fkey | t | fk_partitioned_fk_3
1741+
fk_partitioned_fk_3_1_a_b_fkey | t | fk_partitioned_fk_3_1
1742+
(5 rows)
1743+
1744+
DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
1745+
-- NOT VALID foreign key on a non-partitioned table referencing a partitioned table
1746+
CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
1747+
CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
1748+
CREATE TABLE fk_notpartitioned_fk (b int, a int);
1749+
ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
1750+
-- Constraint will be invalid.
1751+
SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass;
1752+
conname | convalidated
1753+
--------------------------------+--------------
1754+
fk_notpartitioned_fk_a_b_fkey | f
1755+
fk_notpartitioned_fk_a_b_fkey1 | f
1756+
(2 rows)
1757+
1758+
ALTER TABLE fk_notpartitioned_fk VALIDATE CONSTRAINT fk_notpartitioned_fk_a_b_fkey;
1759+
-- All constraints are now valid.
1760+
SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass;
1761+
conname | convalidated
1762+
--------------------------------+--------------
1763+
fk_notpartitioned_fk_a_b_fkey | t
1764+
fk_notpartitioned_fk_a_b_fkey1 | t
1765+
(2 rows)
1766+
1767+
DROP TABLE fk_notpartitioned_fk, fk_partitioned_pk;
16831768
-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE
16841769
-- actions
16851770
CREATE TABLE fk_notpartitioned_pk (a int, b int, primary key (a, b));

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp