From c80eabbaf55edb4c44729499e9c9cd16596f1ddf Mon Sep 17 00:00:00 2001 From: Amul Sul Date: Mon, 10 Feb 2025 10:59:28 +0530 Subject: [PATCH v18.1 3/3] Merge the parent and child constraints with differing enforcibility. If an ENFORCED parent constraint is attached to a NOT ENFORCED child constraint, the child constraint will be made ENFORCED, with validation applied if the parent constraint is validated as well. Otherwise, a new ENFORCED constraint (with validation, if the parent constraint is validated) would need to be created on the child table, which would be unnecessary if a similar constraint already exists and can be attached. On the other hand, having a NOT ENFORCED parent constraint with an ENFORCED child constraint does not cause any issues, and no changes are required. ---- NOTE: This patch is intended to reduce the diff noise from the main patch and is not meant to be committed separately. It should be squashed with the main patch that adds ENFORCED/NOT ENFORCED. ---- --- src/backend/commands/tablecmds.c | 164 ++++++++++++++++++++-- src/test/regress/expected/foreign_key.out | 77 ++++++++-- src/test/regress/sql/foreign_key.sql | 37 ++++- 3 files changed, 253 insertions(+), 25 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6b56d59e77d..e2707132771 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -11543,7 +11543,6 @@ tryAttachPartitionForeignKey(List **wqueue, if (OidIsValid(partConstr->conparentid) || partConstr->condeferrable != parentConstr->condeferrable || partConstr->condeferred != parentConstr->condeferred || - partConstr->conenforced != parentConstr->conenforced || partConstr->confupdtype != parentConstr->confupdtype || partConstr->confdeltype != parentConstr->confdeltype || partConstr->confmatchtype != parentConstr->confmatchtype) @@ -11588,6 +11587,8 @@ AttachPartitionForeignKey(List **wqueue, Oid partConstrFrelid; Oid partConstrRelid; bool parentConstrIsEnforced; + bool partConstrIsEnforced; + bool partConstrParentIsSet; /* Fetch the parent constraint tuple */ parentConstrTup = SearchSysCache1(CONSTROID, @@ -11603,13 +11604,47 @@ AttachPartitionForeignKey(List **wqueue, if (!HeapTupleIsValid(partcontup)) elog(ERROR, "cache lookup failed for constraint %u", partConstrOid); partConstr = (Form_pg_constraint) GETSTRUCT(partcontup); + partConstrIsEnforced = partConstr->conenforced; partConstrFrelid = partConstr->confrelid; partConstrRelid = partConstr->conrelid; + /* + * The case where the parent constraint is NOT ENFORCED and the child + * constraint is ENFORCED is acceptable because the not enforced parent + * constraint lacks triggers, eliminating any redundancy issues with the + * enforced child constraint. In this scenario, the child constraint + * remains enforced, and its trigger is retained, ensuring that + * referential integrity checks for the child continue as before, even + * with the parent constraint not enforced. The relationship between the + * two constraints is preserved by setting the parent constraint, which + * allows us to locate the child constraint. This becomes important if the + * parent constraint is later changed to enforced, at which point the + * necessary trigger will be created for the parent, and any redundancy + * from these triggers will be appropriately handled. + */ + if (!parentConstrIsEnforced && partConstrIsEnforced) + { + ReleaseSysCache(partcontup); + ReleaseSysCache(parentConstrTup); + + ConstraintSetParentConstraint(partConstrOid, parentConstrOid, + RelationGetRelid(partition)); + CommandCounterIncrement(); + + return; + } + /* * If the referenced table is partitioned, then the partition we're * attaching now has extra pg_constraint rows and action triggers that are * no longer needed. Remove those. + * + * Note that this must be done beforehand, particularly in situations + * where we might decide to change the constraint to an ENFORCED state + * which will create the required triggers and add the child constraint to + * the validation queue. To avoid generating unnecessary triggers and + * adding them to the validation queue, it is crucial to eliminate any + * redundant constraints beforehand. */ if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE) { @@ -11628,6 +11663,53 @@ AttachPartitionForeignKey(List **wqueue, */ queueValidation = parentConstr->convalidated && !partConstr->convalidated; + /* + * The case where the parent constraint is ENFORCED and the child + * constraint is NOT ENFORCED is not acceptable, as it would violate + * referential integrity. In such cases, the child constraint will first + * be enforced before merging it with the enforced parent constraint. + * Subsequently, removing action triggers, setting up constraint triggers, + * and handling check triggers for the parent will be managed in the usual + * manner, similar to how two enforced constraints are merged. + */ + if (parentConstrIsEnforced && !partConstrIsEnforced) + { + ATAlterConstraint *cmdcon = makeNode(ATAlterConstraint); + Relation conrel; + + cmdcon->conname = NameStr(partConstr->conname); + cmdcon->deferrable = partConstr->condeferrable; + cmdcon->initdeferred = partConstr->condeferred; + cmdcon->alterEnforceability = true; + cmdcon->is_enforced = true; + + conrel = table_open(ConstraintRelationId, RowExclusiveLock); + + ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, trigrel, + partConstr->conrelid, + partConstr->confrelid, + partcontup, AccessExclusiveLock, + InvalidOid, InvalidOid, InvalidOid, + InvalidOid); + + table_close(conrel, RowExclusiveLock); + + CommandCounterIncrement(); + + /* + * No further validation is needed, as changing the constraint to + * enforced will implicitly trigger the same validation. + */ + queueValidation = false; + } + + /* + * The constraint parent shouldn't be set beforehand, or if it's already + * set, it should be the specified parent. + */ + partConstrParentIsSet = OidIsValid(partConstr->conparentid); + Assert(!partConstrParentIsSet || partConstr->conparentid == parentConstrOid); + ReleaseSysCache(partcontup); ReleaseSysCache(parentConstrTup); @@ -11640,8 +11722,10 @@ AttachPartitionForeignKey(List **wqueue, DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid, partConstrRelid); - ConstraintSetParentConstraint(partConstrOid, parentConstrOid, - RelationGetRelid(partition)); + /* Skip if the parent is already set */ + if (!partConstrParentIsSet) + ConstraintSetParentConstraint(partConstrOid, parentConstrOid, + RelationGetRelid(partition)); /* * Like the constraint, attach partition's "check" triggers to the @@ -12241,6 +12325,17 @@ ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon, /* Drop all the triggers */ DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid); + + /* + * If the referenced table is partitioned, the child constraint we're + * changing to NOT ENFORCED may have additional pg_constraint rows and + * action triggers that remain untouched while this child constraint + * is attached to the NOT ENFORCED parent. These must now be removed. + * For more details, see AttachPartitionForeignKey(). + */ + if (OidIsValid(currcon->conparentid) && + get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE) + RemoveInheritedConstraint(conrel, tgrel, currcon->conrelid, conoid); } else if (changed) /* Create triggers */ { @@ -12566,13 +12661,40 @@ AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon, true, NULL, 1, &pkey); while (HeapTupleIsValid(childtup = systable_getnext(pscan))) - ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid, - pkrelid, childtup, lockmode, - ReferencedParentDelTrigger, - ReferencedParentUpdTrigger, - ReferencingParentInsTrigger, - ReferencingParentUpdTrigger); + { + Form_pg_constraint childcon; + + childcon = (Form_pg_constraint) GETSTRUCT(childtup); + + /* + * When the parent constraint is modified to be ENFORCED, and the + * child constraint is attached to the parent constraint (which is + * already ENFORCED), some constraints and action triggers on the + * child table may become redundant and need to be removed. + */ + if (cmdcon->is_enforced && childcon->conenforced) + { + if (currcon->confrelid == pkrelid) + { + Relation rel = table_open(childcon->conrelid, lockmode); + + AttachPartitionForeignKey(wqueue, rel, childcon->oid, + conoid, + ReferencingParentInsTrigger, + ReferencingParentUpdTrigger, + tgrel); + table_close(rel, NoLock); + } + } + else + ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid, + pkrelid, childtup, lockmode, + ReferencedParentDelTrigger, + ReferencedParentUpdTrigger, + ReferencingParentInsTrigger, + ReferencingParentUpdTrigger); + } systable_endscan(pscan); } @@ -20746,7 +20868,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent, { ForeignKeyCacheInfo *fk = lfirst(cell); HeapTuple contup; + HeapTuple parentContup; Form_pg_constraint conform; + Oid parentConstrIsEnforced; contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid)); if (!HeapTupleIsValid(contup)) @@ -20765,12 +20889,34 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent, continue; } + /* Get the enforcibility of the parent constraint */ + parentContup = SearchSysCache1(CONSTROID, + ObjectIdGetDatum(conform->conparentid)); + if (!HeapTupleIsValid(parentContup)) + elog(ERROR, "cache lookup failed for constraint %u", + conform->conparentid); + parentConstrIsEnforced = + ((Form_pg_constraint) GETSTRUCT(parentContup))->conenforced; + ReleaseSysCache(parentContup); + /* * The constraint on this table must be marked no longer a child of * the parent's constraint, as do its check triggers. */ ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid); + /* + * Unsetting the parent is sufficient when the parent constraint is + * NOT ENFORCED and the child constraint is ENFORCED, as we link them + * by setting the constraint parent, while leaving the rest unchanged. + * For more details, see AttachPartitionForeignKey(). + */ + if (!parentConstrIsEnforced && fk->conenforced) + { + ReleaseSysCache(contup); + continue; + } + /* * Also, look up the partition's "check" triggers corresponding to the * ENFORCED constraint being detached and detach them from the parent diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 1d0a4548732..e4e45d0668a 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1673,7 +1673,6 @@ ALTER TABLE fk_partitioned_fk ADD CONSTRAINT fk_partitioned_fk_a_b_fkey FOREIGN REFERENCES fk_notpartitioned_pk NOT ENFORCED; CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int); ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2; -ALTER TABLE fk_partitioned_fk_2 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000); ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED; CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int) @@ -1682,8 +1681,65 @@ ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2, DROP COLUMN fdrop3, DROP COLUMN fdrop4; CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0); CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1); +-- Merge the not-enforced parent constraint with the enforced and not-enforced child constraints +ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ENFORCED; +ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; +ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; +-- Merge the non-enforced parent constraint with both the enforced and +-- non-enforced child constraints, where the referenced table is partitioned. +CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b); +CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000); +-- Merge the enforced parent constraint with the enforced and not-enforced child constraints. +ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED; +ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT ENFORCED; +ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED; +SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass +FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid; + conname | conenforced | convalidated | conrelid | confrelid +---------------------------------+-------------+--------------+-----------------------+---------------------- + fk_partitioned_fk_3_0_a_b_fkey | t | t | fk_partitioned_fk_3_0 | fk_notpartitioned_pk + fk_partitioned_fk_3_1_a_b_fkey | f | f | fk_partitioned_fk_3_1 | fk_notpartitioned_pk + fk_partitioned_fk_3_a_b_fkey | f | f | fk_partitioned_fk_3 | fk_notpartitioned_pk + fk_partitioned_fk_3_0_a_b_fkey1 | t | t | fk_partitioned_fk_3_0 | fk_partitioned_pk + fk_partitioned_fk_3_1_a_b_fkey1 | t | t | fk_partitioned_fk_3_1 | fk_partitioned_pk + fk_partitioned_fk_3_a_b_fkey1 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk + fk_partitioned_fk_3_a_b_fkey2 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk_1 +(7 rows) + +ALTER TABLE fk_partitioned_fk_3 DETACH PARTITION fk_partitioned_fk_3_0; +ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_0 FOR VALUES WITH (MODULUS 5, REMAINDER 0); +SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass +FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid; + conname | conenforced | convalidated | conrelid | confrelid +---------------------------------+-------------+--------------+-----------------------+---------------------- + fk_partitioned_fk_3_0_a_b_fkey | t | t | fk_partitioned_fk_3_0 | fk_notpartitioned_pk + fk_partitioned_fk_3_1_a_b_fkey | f | f | fk_partitioned_fk_3_1 | fk_notpartitioned_pk + fk_partitioned_fk_3_a_b_fkey | f | f | fk_partitioned_fk_3 | fk_notpartitioned_pk + fk_partitioned_fk_3_0_a_b_fkey1 | t | t | fk_partitioned_fk_3_0 | fk_partitioned_pk + fk_partitioned_fk_3_1_a_b_fkey1 | t | t | fk_partitioned_fk_3_1 | fk_partitioned_pk + fk_partitioned_fk_3_a_b_fkey1 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk + fk_partitioned_fk_3_a_b_fkey2 | t | t | fk_partitioned_fk_3 | fk_partitioned_pk_1 +(7 rows) + +ALTER TABLE fk_partitioned_fk_3 ALTER CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 NOT ENFORCED; +-- Merging an enforced parent constraint (validated) with a not-enforced child +-- constraint will implicitly change the child constraint to enforced and apply +-- the validation as well. ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FOR VALUES FROM (2000,2000) TO (3000,3000); +SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass +FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid; + conname | conenforced | convalidated | conrelid | confrelid +---------------------------------+-------------+--------------+-----------------------+---------------------- + fk_partitioned_fk_3_0_a_b_fkey | t | t | fk_partitioned_fk_3_0 | fk_notpartitioned_pk + fk_partitioned_fk_3_1_a_b_fkey | t | t | fk_partitioned_fk_3_1 | fk_notpartitioned_pk + fk_partitioned_fk_3_a_b_fkey | t | t | fk_partitioned_fk_3 | fk_notpartitioned_pk + fk_partitioned_fk_3_0_a_b_fkey1 | f | f | fk_partitioned_fk_3_0 | fk_partitioned_pk + fk_partitioned_fk_3_1_a_b_fkey1 | f | f | fk_partitioned_fk_3_1 | fk_partitioned_pk + fk_partitioned_fk_3_a_b_fkey1 | f | f | fk_partitioned_fk_3 | fk_partitioned_pk + fk_partitioned_fk_3_a_b_fkey2 | f | f | fk_partitioned_fk_3 | fk_partitioned_pk_1 +(7 rows) + -- Creating a foreign key with ONLY on a partitioned table referencing -- a non-partitioned table fails. ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b) @@ -1698,22 +1754,22 @@ INSERT INTO fk_partitioned_fk_1 (a,b) VALUES (500, 501); ERROR: insert or update on table "fk_partitioned_fk_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" DETAIL: Key (a, b)=(500, 501) is not present in table "fk_notpartitioned_pk". INSERT INTO fk_partitioned_fk (a,b) VALUES (1500, 1501); -ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_2_a_b_fkey" +ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" DETAIL: Key (a, b)=(1500, 1501) is not present in table "fk_notpartitioned_pk". INSERT INTO fk_partitioned_fk_2 (a,b) VALUES (1500, 1501); -ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_2_a_b_fkey" +ERROR: insert or update on table "fk_partitioned_fk_2" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" DETAIL: Key (a, b)=(1500, 1501) is not present in table "fk_notpartitioned_pk". INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502); -ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" +ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey" DETAIL: Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk". INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2500, 2502); -ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" +ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey" DETAIL: Key (a, b)=(2500, 2502) is not present in table "fk_notpartitioned_pk". INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503); -ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" +ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_3_0_a_b_fkey" DETAIL: Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk". INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2501, 2503); -ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" +ERROR: insert or update on table "fk_partitioned_fk_3_0" violates foreign key constraint "fk_partitioned_fk_3_0_a_b_fkey" DETAIL: Key (a, b)=(2501, 2503) is not present in table "fk_notpartitioned_pk". -- but if we insert the values that make them valid, then they work INSERT INTO fk_notpartitioned_pk VALUES (500, 501), (1500, 1501), @@ -1724,7 +1780,7 @@ INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502); INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503); -- this update fails because there is no referenced row UPDATE fk_partitioned_fk SET a = a + 1 WHERE a = 2501; -ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_a_b_fkey" +ERROR: insert or update on table "fk_partitioned_fk_3_1" violates foreign key constraint "fk_partitioned_fk_3_1_a_b_fkey" DETAIL: Key (a, b)=(2502, 2503) is not present in table "fk_notpartitioned_pk". -- but we can fix it thusly: INSERT INTO fk_notpartitioned_pk (a,b) VALUES (2502, 2503); @@ -1867,8 +1923,6 @@ WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid::regclass: DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk; -- NOT VALID foreign key on a non-partitioned table referencing a partitioned table -CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b); -CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000); CREATE TABLE fk_notpartitioned_fk (b int, a int); ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID; -- Constraint will be invalid. @@ -2084,7 +2138,7 @@ CREATE TABLE fk_partitioned_fk_2 (b int, a int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED); BEGIN; ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502); --- should be having two constraints +-- should only have one constraint \d fk_partitioned_fk_2 Table "public.fk_partitioned_fk_2" Column | Type | Collation | Nullable | Default @@ -2093,7 +2147,6 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN a | integer | | | Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502) Foreign-key constraints: - "fk_partitioned_fk_2_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED TABLE "fk_partitioned_fk" CONSTRAINT "fk_partitioned_fk_a_b_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DROP TABLE fk_partitioned_fk_2; diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 7e5be0702da..fa51c7110ef 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1248,18 +1248,49 @@ CREATE TABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b i REFERENCES fk_notpartitioned_pk NOT ENFORCED; CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int); ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2; -ALTER TABLE fk_partitioned_fk_2 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000); ALTER TABLE fk_partitioned_fk ALTER CONSTRAINT fk_partitioned_fk_a_b_fkey ENFORCED; + CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int) PARTITION BY HASH (a); ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2, DROP COLUMN fdrop3, DROP COLUMN fdrop4; CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0); CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1); +-- Merge the not-enforced parent constraint with the enforced and not-enforced child constraints +ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ENFORCED; +ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; +ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT ENFORCED; + +-- Merge the non-enforced parent constraint with both the enforced and +-- non-enforced child constraints, where the referenced table is partitioned. +CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b); +CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000); +-- Merge the enforced parent constraint with the enforced and not-enforced child constraints. +ALTER TABLE fk_partitioned_fk_3_0 ADD CONSTRAINT fk_partitioned_fk_3_0_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED; +ALTER TABLE fk_partitioned_fk_3_1 ADD CONSTRAINT fk_partitioned_fk_3_1_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT ENFORCED; +ALTER TABLE fk_partitioned_fk_3 ADD CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk ENFORCED; + +SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass +FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid; + +ALTER TABLE fk_partitioned_fk_3 DETACH PARTITION fk_partitioned_fk_3_0; +ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_0 FOR VALUES WITH (MODULUS 5, REMAINDER 0); + +SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass +FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid; + +ALTER TABLE fk_partitioned_fk_3 ALTER CONSTRAINT fk_partitioned_fk_3_a_b_fkey1 NOT ENFORCED; + +-- Merging an enforced parent constraint (validated) with a not-enforced child +-- constraint will implicitly change the child constraint to enforced and apply +-- the validation as well. ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FOR VALUES FROM (2000,2000) TO (3000,3000); +SELECT conname, conenforced, convalidated, conrelid::regclass, confrelid::regclass +FROM pg_constraint WHERE conrelid::regclass::text like 'fk_partitioned_fk_3%' ORDER BY oid; + -- Creating a foreign key with ONLY on a partitioned table referencing -- a non-partitioned table fails. ALTER TABLE ONLY fk_partitioned_fk ADD FOREIGN KEY (a, b) @@ -1386,8 +1417,6 @@ CREATE TABLE fk_partitioned_fk_3_1 (a int, b int); DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk; -- NOT VALID foreign key on a non-partitioned table referencing a partitioned table -CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b); -CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000); CREATE TABLE fk_notpartitioned_fk (b int, a int); ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID; @@ -1528,7 +1557,7 @@ CREATE TABLE fk_partitioned_fk_2 (b int, a int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE NOT ENFORCED); BEGIN; ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502); --- should be having two constraints +-- should only have one constraint \d fk_partitioned_fk_2 DROP TABLE fk_partitioned_fk_2; ROLLBACK; -- 2.49.0