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

Commit614a406

Browse files
committed
Fix self-referencing foreign keys with partitioned tables
There are a number of bugs in this area. Two of them are fixed here,namely:1. get_relation_idx_constraint_oid does not restrict the type of constraint that's returned, so with sufficient bad luck it can return the OID of a foreign key constraint. This has the effect that a primary key in a partition can end up as a child of a foreign key, which makes no sense (it needs to be the child of the equivalent primary key.) Change the API contract so that only index-backed constraints are returned, mimicking get_constraint_index().2. Both CloneFkReferenced and CloneFkReferencing clone a self-referencing foreign key, so the partition ends up with a duplicate foreign key. Change the former function to ignore such constraints.Add some tests to verify that things are better now. (However, thesenew tests show some additional misbehavior that will be fixed later --namely that there's a constraint marked NOT VALID.)Backpatch to 12, where these constraints are possible at all.Author: Jehan-Guillaume de Rorthais <jgdr@dalibo.com>Discussion:https://postgr.es/m/20220603154232.1715b14c@karst
1 parent3edc71e commit614a406

File tree

4 files changed

+161
-1
lines changed

4 files changed

+161
-1
lines changed

‎src/backend/catalog/pg_constraint.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,8 +985,12 @@ get_relation_constraint_attnos(Oid relid, const char *conname,
985985
}
986986

987987
/*
988-
* Return the OID of the constraintassociated with the given index in the
988+
* Return the OID of the constraintenforced by the given index in the
989989
* given relation; or InvalidOid if no such index is catalogued.
990+
*
991+
* Much like get_constraint_index, this function is concerned only with the
992+
* one constraint that "owns" the given index. Therefore, constraints of
993+
* types other than unique, primary-key, and exclusion are ignored.
990994
*/
991995
Oid
992996
get_relation_idx_constraint_oid(OidrelationId,OidindexId)
@@ -1011,6 +1015,13 @@ get_relation_idx_constraint_oid(Oid relationId, Oid indexId)
10111015
Form_pg_constraintconstrForm;
10121016

10131017
constrForm= (Form_pg_constraint)GETSTRUCT(tuple);
1018+
1019+
/* See above */
1020+
if (constrForm->contype!=CONSTRAINT_PRIMARY&&
1021+
constrForm->contype!=CONSTRAINT_UNIQUE&&
1022+
constrForm->contype!=CONSTRAINT_EXCLUSION)
1023+
continue;
1024+
10141025
if (constrForm->conindid==indexId)
10151026
{
10161027
constraintId=constrForm->oid;

‎src/backend/commands/tablecmds.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9968,6 +9968,8 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
99689968
* clone those constraints to the given partition. This is to be called
99699969
* when the partition is being created or attached.
99709970
*
9971+
* This ignores self-referencing FKs; those are handled by CloneFkReferencing.
9972+
*
99719973
* This recurses to partitions, if the relation being attached is partitioned.
99729974
* Recursion is done by calling addFkRecurseReferenced.
99739975
*/
@@ -10056,6 +10058,17 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
1005610058
continue;
1005710059
}
1005810060

10061+
/*
10062+
* Don't clone self-referencing foreign keys, which can be in the
10063+
* partitioned table or in the partition-to-be.
10064+
*/
10065+
if (constrForm->conrelid == RelationGetRelid(parentRel) ||
10066+
constrForm->conrelid == RelationGetRelid(partitionRel))
10067+
{
10068+
ReleaseSysCache(tuple);
10069+
continue;
10070+
}
10071+
1005910072
/*
1006010073
* Because we're only expanding the key space at the referenced side,
1006110074
* we don't need to prevent any operation in the referencing table, so

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,6 +1992,87 @@ drop table other_partitioned_fk;
19921992
reset role;
19931993
revoke all on fk_notpartitioned_pk from regress_other_partitioned_fk_owner;
19941994
drop role regress_other_partitioned_fk_owner;
1995+
--
1996+
-- Test self-referencing foreign key with partition.
1997+
-- This should create only one fk constraint per partition
1998+
--
1999+
CREATE TABLE parted_self_fk (
2000+
id bigint NOT NULL PRIMARY KEY,
2001+
id_abc bigint,
2002+
FOREIGN KEY (id_abc) REFERENCES parted_self_fk(id)
2003+
)
2004+
PARTITION BY RANGE (id);
2005+
CREATE TABLE part1_self_fk (
2006+
id bigint NOT NULL PRIMARY KEY,
2007+
id_abc bigint
2008+
);
2009+
ALTER TABLE parted_self_fk ATTACH PARTITION part1_self_fk FOR VALUES FROM (0) TO (10);
2010+
CREATE TABLE part2_self_fk PARTITION OF parted_self_fk FOR VALUES FROM (10) TO (20);
2011+
CREATE TABLE part3_self_fk (-- a partitioned partition
2012+
id bigint NOT NULL PRIMARY KEY,
2013+
id_abc bigint
2014+
) PARTITION BY RANGE (id);
2015+
CREATE TABLE part32_self_fk PARTITION OF part3_self_fk FOR VALUES FROM (20) TO (30);
2016+
ALTER TABLE parted_self_fk ATTACH PARTITION part3_self_fk FOR VALUES FROM (20) TO (40);
2017+
CREATE TABLE part33_self_fk (
2018+
id bigint NOT NULL PRIMARY KEY,
2019+
id_abc bigint
2020+
);
2021+
ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40);
2022+
SELECT cr.relname, co.conname, co.contype, co.convalidated,
2023+
p.conname AS conparent, p.convalidated, cf.relname AS foreignrel
2024+
FROM pg_constraint co
2025+
JOIN pg_class cr ON cr.oid = co.conrelid
2026+
LEFT JOIN pg_class cf ON cf.oid = co.confrelid
2027+
LEFT JOIN pg_constraint p ON p.oid = co.conparentid
2028+
WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk'))
2029+
ORDER BY co.contype, cr.relname, co.conname, p.conname;
2030+
relname | conname | contype | convalidated | conparent | convalidated | foreignrel
2031+
----------------+----------------------------+---------+--------------+----------------------------+--------------+----------------
2032+
part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2033+
part2_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2034+
part32_self_fk | parted_self_fk_id_abc_fkey | f | f | parted_self_fk_id_abc_fkey | t | parted_self_fk
2035+
part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2036+
part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2037+
parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk
2038+
part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t |
2039+
part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t |
2040+
part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t |
2041+
part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t |
2042+
part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t |
2043+
parted_self_fk | parted_self_fk_pkey | p | t | | |
2044+
(12 rows)
2045+
2046+
-- detach and re-attach multiple times just to ensure everything is kosher
2047+
ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk;
2048+
ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20);
2049+
ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk;
2050+
ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20);
2051+
SELECT cr.relname, co.conname, co.contype, co.convalidated,
2052+
p.conname AS conparent, p.convalidated, cf.relname AS foreignrel
2053+
FROM pg_constraint co
2054+
JOIN pg_class cr ON cr.oid = co.conrelid
2055+
LEFT JOIN pg_class cf ON cf.oid = co.confrelid
2056+
LEFT JOIN pg_constraint p ON p.oid = co.conparentid
2057+
WHERE cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk'))
2058+
ORDER BY co.contype, cr.relname, co.conname, p.conname;
2059+
relname | conname | contype | convalidated | conparent | convalidated | foreignrel
2060+
----------------+----------------------------+---------+--------------+----------------------------+--------------+----------------
2061+
part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2062+
part2_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2063+
part32_self_fk | parted_self_fk_id_abc_fkey | f | f | parted_self_fk_id_abc_fkey | t | parted_self_fk
2064+
part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2065+
part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2066+
parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk
2067+
part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t |
2068+
part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t |
2069+
part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t |
2070+
part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t |
2071+
part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t |
2072+
parted_self_fk | parted_self_fk_pkey | p | t | | |
2073+
(12 rows)
2074+
2075+
-- Leave this table around, for pg_upgrade/pg_dump tests
19952076
-- Test creating a constraint at the parent that already exists in partitions.
19962077
-- There should be no duplicated constraints, and attempts to drop the
19972078
-- constraint in partitions should raise appropriate errors.

‎src/test/regress/sql/foreign_key.sql

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,61 @@ reset role;
14411441
revoke allon fk_notpartitioned_pkfrom regress_other_partitioned_fk_owner;
14421442
drop role regress_other_partitioned_fk_owner;
14431443

1444+
--
1445+
-- Test self-referencing foreign key with partition.
1446+
-- This should create only one fk constraint per partition
1447+
--
1448+
CREATETABLEparted_self_fk (
1449+
idbigintNOT NULLPRIMARY KEY,
1450+
id_abcbigint,
1451+
FOREIGN KEY (id_abc)REFERENCES parted_self_fk(id)
1452+
)
1453+
PARTITION BY RANGE (id);
1454+
CREATETABLEpart1_self_fk (
1455+
idbigintNOT NULLPRIMARY KEY,
1456+
id_abcbigint
1457+
);
1458+
ALTERTABLE parted_self_fk ATTACH PARTITION part1_self_fk FORVALUESFROM (0) TO (10);
1459+
CREATETABLEpart2_self_fk PARTITION OF parted_self_fk FORVALUESFROM (10) TO (20);
1460+
CREATETABLEpart3_self_fk (-- a partitioned partition
1461+
idbigintNOT NULLPRIMARY KEY,
1462+
id_abcbigint
1463+
) PARTITION BY RANGE (id);
1464+
CREATETABLEpart32_self_fk PARTITION OF part3_self_fk FORVALUESFROM (20) TO (30);
1465+
ALTERTABLE parted_self_fk ATTACH PARTITION part3_self_fk FORVALUESFROM (20) TO (40);
1466+
CREATETABLEpart33_self_fk (
1467+
idbigintNOT NULLPRIMARY KEY,
1468+
id_abcbigint
1469+
);
1470+
ALTERTABLE part3_self_fk ATTACH PARTITION part33_self_fk FORVALUESFROM (30) TO (40);
1471+
1472+
SELECTcr.relname,co.conname,co.contype,co.convalidated,
1473+
p.connameAS conparent,p.convalidated,cf.relnameAS foreignrel
1474+
FROM pg_constraint co
1475+
JOIN pg_class crONcr.oid=co.conrelid
1476+
LEFT JOIN pg_class cfONcf.oid=co.confrelid
1477+
LEFT JOIN pg_constraint pONp.oid=co.conparentid
1478+
WHEREcr.oidIN (SELECT relidFROM pg_partition_tree('parted_self_fk'))
1479+
ORDER BYco.contype,cr.relname,co.conname,p.conname;
1480+
1481+
-- detach and re-attach multiple times just to ensure everything is kosher
1482+
ALTERTABLE parted_self_fk DETACH PARTITION part2_self_fk;
1483+
ALTERTABLE parted_self_fk ATTACH PARTITION part2_self_fk FORVALUESFROM (10) TO (20);
1484+
ALTERTABLE parted_self_fk DETACH PARTITION part2_self_fk;
1485+
ALTERTABLE parted_self_fk ATTACH PARTITION part2_self_fk FORVALUESFROM (10) TO (20);
1486+
1487+
SELECTcr.relname,co.conname,co.contype,co.convalidated,
1488+
p.connameAS conparent,p.convalidated,cf.relnameAS foreignrel
1489+
FROM pg_constraint co
1490+
JOIN pg_class crONcr.oid=co.conrelid
1491+
LEFT JOIN pg_class cfONcf.oid=co.confrelid
1492+
LEFT JOIN pg_constraint pONp.oid=co.conparentid
1493+
WHEREcr.oidIN (SELECT relidFROM pg_partition_tree('parted_self_fk'))
1494+
ORDER BYco.contype,cr.relname,co.conname,p.conname;
1495+
1496+
-- Leave this table around, for pg_upgrade/pg_dump tests
1497+
1498+
14441499
-- Test creating a constraint at the parent that already exists in partitions.
14451500
-- There should be no duplicated constraints, and attempts to drop the
14461501
-- constraint in partitions should raise appropriate errors.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp