diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index bb65fb1e0a..3a5d4e9643 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -985,8 +985,12 @@ get_relation_constraint_attnos(Oid relid, const char *conname, } /* - * Return the OID of the constraint associated with the given index in the + * Return the OID of the constraint enforced by the given index in the * given relation; or InvalidOid if no such index is catalogued. + * + * Much like get_constraint_index, this function is concerned only with the + * one constraint that "owns" the given index. Therefore, constraints of + * types other than unique, primary-key, and exclusion are ignored. */ Oid get_relation_idx_constraint_oid(Oid relationId, Oid indexId) @@ -1011,6 +1015,13 @@ get_relation_idx_constraint_oid(Oid relationId, Oid indexId) Form_pg_constraint constrForm; constrForm = (Form_pg_constraint) GETSTRUCT(tuple); + + /* See above */ + if (constrForm->contype != CONSTRAINT_PRIMARY && + constrForm->contype != CONSTRAINT_UNIQUE && + constrForm->contype != CONSTRAINT_EXCLUSION) + continue; + if (constrForm->conindid == indexId) { constraintId = constrForm->oid; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1f774ac065..20135ef1b0 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9968,6 +9968,8 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel, * clone those constraints to the given partition. This is to be called * when the partition is being created or attached. * + * This ignores self-referencing FKs; those are handled by CloneFkReferencing. + * * This recurses to partitions, if the relation being attached is partitioned. * Recursion is done by calling addFkRecurseReferenced. */ @@ -10056,6 +10058,17 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel) continue; } + /* + * Don't clone self-referencing foreign keys, which can be in the + * partitioned table or in the partition-to-be. + */ + if (constrForm->conrelid == RelationGetRelid(parentRel) || + constrForm->conrelid == RelationGetRelid(partitionRel)) + { + ReleaseSysCache(tuple); + continue; + } + /* * Because we're only expanding the key space at the referenced side, * we don't need to prevent any operation in the referencing table, so diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 8bb3e58494..ae5276ff17 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1992,6 +1992,87 @@ drop table other_partitioned_fk; reset role; revoke all on fk_notpartitioned_pk from regress_other_partitioned_fk_owner; drop role regress_other_partitioned_fk_owner; +-- +-- Test self-referencing foreign key with partition. +-- This should create only one fk constraint per partition +-- +CREATE TABLE parted_self_fk ( + id bigint NOT NULL PRIMARY KEY, + id_abc bigint, + FOREIGN KEY (id_abc) REFERENCES parted_self_fk(id) +) +PARTITION BY RANGE (id); +CREATE TABLE part1_self_fk ( + id bigint NOT NULL PRIMARY KEY, + id_abc bigint +); +ALTER TABLE parted_self_fk ATTACH PARTITION part1_self_fk FOR VALUES FROM (0) TO (10); +CREATE TABLE part2_self_fk PARTITION OF parted_self_fk FOR VALUES FROM (10) TO (20); +CREATE TABLE part3_self_fk ( -- a partitioned partition + id bigint NOT NULL PRIMARY KEY, + id_abc bigint +) PARTITION BY RANGE (id); +CREATE TABLE part32_self_fk PARTITION OF part3_self_fk FOR VALUES FROM (20) TO (30); +ALTER TABLE parted_self_fk ATTACH PARTITION part3_self_fk FOR VALUES FROM (20) TO (40); +CREATE TABLE part33_self_fk ( + id bigint NOT NULL PRIMARY KEY, + id_abc bigint +); +ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40); +SELECT cr.relname, co.conname, co.contype, co.convalidated, + p.conname AS conparent, p.convalidated, cf.relname AS foreignrel +FROM pg_constraint co +JOIN pg_class cr ON cr.oid = co.conrelid +LEFT JOIN pg_class cf ON cf.oid = co.confrelid +LEFT JOIN pg_constraint p ON p.oid = co.conparentid +WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY co.contype, cr.relname, co.conname, p.conname; + relname | conname | contype | convalidated | conparent | convalidated | foreignrel +----------------+----------------------------+---------+--------------+----------------------------+--------------+---------------- + part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part2_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part32_self_fk | parted_self_fk_id_abc_fkey | f | f | parted_self_fk_id_abc_fkey | t | parted_self_fk + part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk + part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t | + part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t | + part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t | + part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t | + part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t | + parted_self_fk | parted_self_fk_pkey | p | t | | | +(12 rows) + +-- detach and re-attach multiple times just to ensure everything is kosher +ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; +ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); +ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; +ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); +SELECT cr.relname, co.conname, co.contype, co.convalidated, + p.conname AS conparent, p.convalidated, cf.relname AS foreignrel +FROM pg_constraint co +JOIN pg_class cr ON cr.oid = co.conrelid +LEFT JOIN pg_class cf ON cf.oid = co.confrelid +LEFT JOIN pg_constraint p ON p.oid = co.conparentid +WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY co.contype, cr.relname, co.conname, p.conname; + relname | conname | contype | convalidated | conparent | convalidated | foreignrel +----------------+----------------------------+---------+--------------+----------------------------+--------------+---------------- + part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part2_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part32_self_fk | parted_self_fk_id_abc_fkey | f | f | parted_self_fk_id_abc_fkey | t | parted_self_fk + part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk + parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk + part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t | + part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t | + part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t | + part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t | + part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t | + parted_self_fk | parted_self_fk_pkey | p | t | | | +(12 rows) + +-- Leave this table around, for pg_upgrade/pg_dump tests -- Test creating a constraint at the parent that already exists in partitions. -- There should be no duplicated constraints, and attempts to drop the -- constraint in partitions should raise appropriate errors. diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 6e1fddbbd2..90f6db7f21 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1441,6 +1441,61 @@ reset role; revoke all on fk_notpartitioned_pk from regress_other_partitioned_fk_owner; drop role regress_other_partitioned_fk_owner; +-- +-- Test self-referencing foreign key with partition. +-- This should create only one fk constraint per partition +-- +CREATE TABLE parted_self_fk ( + id bigint NOT NULL PRIMARY KEY, + id_abc bigint, + FOREIGN KEY (id_abc) REFERENCES parted_self_fk(id) +) +PARTITION BY RANGE (id); +CREATE TABLE part1_self_fk ( + id bigint NOT NULL PRIMARY KEY, + id_abc bigint +); +ALTER TABLE parted_self_fk ATTACH PARTITION part1_self_fk FOR VALUES FROM (0) TO (10); +CREATE TABLE part2_self_fk PARTITION OF parted_self_fk FOR VALUES FROM (10) TO (20); +CREATE TABLE part3_self_fk ( -- a partitioned partition + id bigint NOT NULL PRIMARY KEY, + id_abc bigint +) PARTITION BY RANGE (id); +CREATE TABLE part32_self_fk PARTITION OF part3_self_fk FOR VALUES FROM (20) TO (30); +ALTER TABLE parted_self_fk ATTACH PARTITION part3_self_fk FOR VALUES FROM (20) TO (40); +CREATE TABLE part33_self_fk ( + id bigint NOT NULL PRIMARY KEY, + id_abc bigint +); +ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40); + +SELECT cr.relname, co.conname, co.contype, co.convalidated, + p.conname AS conparent, p.convalidated, cf.relname AS foreignrel +FROM pg_constraint co +JOIN pg_class cr ON cr.oid = co.conrelid +LEFT JOIN pg_class cf ON cf.oid = co.confrelid +LEFT JOIN pg_constraint p ON p.oid = co.conparentid +WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY co.contype, cr.relname, co.conname, p.conname; + +-- detach and re-attach multiple times just to ensure everything is kosher +ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; +ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); +ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk; +ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20); + +SELECT cr.relname, co.conname, co.contype, co.convalidated, + p.conname AS conparent, p.convalidated, cf.relname AS foreignrel +FROM pg_constraint co +JOIN pg_class cr ON cr.oid = co.conrelid +LEFT JOIN pg_class cf ON cf.oid = co.confrelid +LEFT JOIN pg_constraint p ON p.oid = co.conparentid +WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk')) +ORDER BY co.contype, cr.relname, co.conname, p.conname; + +-- Leave this table around, for pg_upgrade/pg_dump tests + + -- Test creating a constraint at the parent that already exists in partitions. -- There should be no duplicated constraints, and attempts to drop the -- constraint in partitions should raise appropriate errors.