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

Commit1649d15

Browse files
author
Álvaro Herrera
committed
Handle self-referencing FKs correctly in partitioned tables
For self-referencing foreign keys in partitioned tables, we weren'thandling creation of pg_constraint rows during CREATE TABLE PARTITION ASas well as ALTER TABLE ATTACH PARTITION. This is an old bug -- mostly,we broke this in614a406 while trying to fix it (so 12.13, 13.9,14.6 and 15.0 and up all behave incorrectly). This commit reverts partof that with additional fixes for full correctness, and installs moretests to verify the parts we broke, not just the catalog contents butalso the user-visible behavior.Backpatch to all live branches. In branches 13 and 14, commit46a8c27 changed the behavior during DETACH to drop a FKconstraint rather than trying to repair it, because the complete fix ofrepairing catalog constraints was problematic due to lack of previousfixes. For this reason, the test behavior in those branches is a bitdifferent. However, as best as I can tell, the fix works correctlythere.In release notes we have to recommend that all self-referencing foreignkeys on partitioned tables be recreated if partitions have been createdor attached after the FK was created, keeping in mind that violatingrows might already be present on the referencing side.Reported-by: Guillaume Lelarge <guillaume@lelarge.info>Reported-by: Matthew Gabeler-Lee <fastcat@gmail.com>Reported-by: Luca Vallisa <luca.vallisa@gmail.com>Discussion:https://postgr.es/m/CAECtzeWHCA+6tTcm2Oh2+g7fURUJpLZb-=pRXgeWJ-Pi+VU=_w@mail.gmail.comDiscussion:https://postgr.es/m/18156-a44bc7096f0683e6@postgresql.orgDiscussion:https://postgr.es/m/CAAT=myvsiF-Attja5DcWoUWh21R12R-sfXECY2-3ynt8kaOqjw@mail.gmail.com
1 parent16f911c commit1649d15

File tree

4 files changed

+126
-69
lines changed

4 files changed

+126
-69
lines changed

‎src/backend/commands/tablecmds.c

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9864,14 +9864,14 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
98649864
Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
98659865

98669866
/*
9867-
*Cloneconstraintsfor whichthe parent is on thereferenced side.
9867+
*First, cloneconstraintswherethe parent is on thereferencing side.
98689868
*/
9869-
CloneFkReferenced(parentRel, partitionRel);
9869+
CloneFkReferencing(wqueue,parentRel, partitionRel);
98709870

98719871
/*
9872-
*Now cloneconstraintswherethe parent is on thereferencing side.
9872+
*Cloneconstraintsfor whichthe parent is on thereferenced side.
98739873
*/
9874-
CloneFkReferencing(wqueue,parentRel, partitionRel);
9874+
CloneFkReferenced(parentRel, partitionRel);
98759875
}
98769876

98779877
/*
@@ -9882,8 +9882,6 @@ CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
98829882
* clone those constraints to the given partition. This is to be called
98839883
* when the partition is being created or attached.
98849884
*
9885-
* This ignores self-referencing FKs; those are handled by CloneFkReferencing.
9886-
*
98879885
* This recurses to partitions, if the relation being attached is partitioned.
98889886
* Recursion is done by calling addFkRecurseReferenced.
98899887
*/
@@ -9959,17 +9957,6 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
99599957
continue;
99609958
}
99619959

9962-
/*
9963-
* Don't clone self-referencing foreign keys, which can be in the
9964-
* partitioned table or in the partition-to-be.
9965-
*/
9966-
if (constrForm->conrelid == RelationGetRelid(parentRel) ||
9967-
constrForm->conrelid == RelationGetRelid(partitionRel))
9968-
{
9969-
ReleaseSysCache(tuple);
9970-
continue;
9971-
}
9972-
99739960
/*
99749961
* Because we're only expanding the key space at the referenced side,
99759962
* we don't need to prevent any operation in the referencing table, so

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

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,58 +1970,98 @@ CREATE TABLE part33_self_fk (
19701970
id_abc bigint
19711971
);
19721972
ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40);
1973-
SELECT cr.relname, co.conname, co.contype, co.convalidated,
1973+
-- verify that this constraint works
1974+
INSERT INTO parted_self_fk VALUES (1, NULL), (2, NULL), (3, NULL);
1975+
INSERT INTO parted_self_fk VALUES (10, 1), (11, 2), (12, 3) RETURNING tableoid::regclass;
1976+
tableoid
1977+
---------------
1978+
part2_self_fk
1979+
part2_self_fk
1980+
part2_self_fk
1981+
(3 rows)
1982+
1983+
INSERT INTO parted_self_fk VALUES (4, 5);-- error: referenced doesn't exist
1984+
ERROR: insert or update on table "part1_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey"
1985+
DETAIL: Key (id_abc)=(5) is not present in table "parted_self_fk".
1986+
DELETE FROM parted_self_fk WHERE id = 1 RETURNING *;-- error: reference remains
1987+
ERROR: update or delete on table "part1_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey1" on table "parted_self_fk"
1988+
DETAIL: Key (id)=(1) is still referenced from table "parted_self_fk".
1989+
SELECT cr.relname, co.conname, co.convalidated,
19741990
p.conname AS conparent, p.convalidated, cf.relname AS foreignrel
19751991
FROM pg_constraint co
19761992
JOIN pg_class cr ON cr.oid = co.conrelid
19771993
LEFT JOIN pg_class cf ON cf.oid = co.confrelid
19781994
LEFT JOIN pg_constraint p ON p.oid = co.conparentid
1979-
WHEREcr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk'))
1980-
ORDER BY co.contype,cr.relname, co.conname, p.conname;
1981-
relname |conname | contype | convalidated | conparent | convalidated | foreignrel
1982-
----------------+----------------------------+---------+--------------+----------------------------+--------------+----------------
1983-
part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
1984-
part2_self_fk | parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
1985-
part32_self_fk| parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
1986-
part33_self_fk | parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
1987-
part3_self_fk| parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
1988-
parted_self_fk| parted_self_fk_id_abc_fkey| f| t | | | parted_self_fk
1989-
part1_self_fk | part1_self_fk_pkey| p| t| parted_self_fk_pkey| t|
1990-
part2_self_fk | part2_self_fk_pkey | p |t |parted_self_fk_pkey| t |
1991-
part32_self_fk |part32_self_fk_pkey | p |t |part3_self_fk_pkey| t |
1992-
part33_self_fk |part33_self_fk_pkey | p |t |part3_self_fk_pkey| t |
1993-
part3_self_fk | part3_self_fk_pkey | p |t |parted_self_fk_pkey| t |
1994-
parted_self_fk |parted_self_fk_pkey | p |t | | |
1995-
(12 rows)
1995+
WHEREco.contype = 'f' AND
1996+
cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk'))
1997+
ORDER BY cr.relname, co.conname, p.conname;
1998+
relname | conname | convalidated | conparent | convalidated | foreignrel
1999+
----------------+-----------------------------+--------------+-----------------------------+--------------+----------------
2000+
part1_self_fk | parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2001+
part2_self_fk| parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2002+
part32_self_fk | parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2003+
part33_self_fk| parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2004+
part3_self_fk| parted_self_fk_id_abc_fkey| t |parted_self_fk_id_abc_fkey | t | parted_self_fk
2005+
parted_self_fk | parted_self_fk_id_abc_fkey | t | | | parted_self_fk
2006+
parted_self_fk | parted_self_fk_id_abc_fkey1 |t |parted_self_fk_id_abc_fkey| t |part1_self_fk
2007+
parted_self_fk |parted_self_fk_id_abc_fkey2 |t |parted_self_fk_id_abc_fkey| t |part2_self_fk
2008+
parted_self_fk |parted_self_fk_id_abc_fkey3 |t |parted_self_fk_id_abc_fkey| t |part3_self_fk
2009+
parted_self_fk | parted_self_fk_id_abc_fkey4 |t |parted_self_fk_id_abc_fkey3| t |part32_self_fk
2010+
parted_self_fk |parted_self_fk_id_abc_fkey5 |t |parted_self_fk_id_abc_fkey3 | t| part33_self_fk
2011+
(11 rows)
19962012

19972013
-- detach and re-attach multiple times just to ensure everything is kosher
19982014
ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk;
2015+
\d+ part2_self_fk
2016+
Table "public.part2_self_fk"
2017+
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
2018+
--------+--------+-----------+----------+---------+---------+--------------+-------------
2019+
id | bigint | | not null | | plain | |
2020+
id_abc | bigint | | | | plain | |
2021+
Indexes:
2022+
"part2_self_fk_pkey" PRIMARY KEY, btree (id)
2023+
2024+
INSERT INTO part2_self_fk VALUES (16, 9);-- good, but it'll prevent the attach below
2025+
ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20);
2026+
ERROR: insert or update on table "part2_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey"
2027+
DETAIL: Key (id_abc)=(9) is not present in table "parted_self_fk".
2028+
DELETE FROM part2_self_fk WHERE id = 16;
19992029
ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20);
2030+
INSERT INTO parted_self_fk VALUES (16, 9);-- error: referenced doesn't exist
2031+
ERROR: insert or update on table "part2_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey"
2032+
DETAIL: Key (id_abc)=(9) is not present in table "parted_self_fk".
2033+
DELETE FROM parted_self_fk WHERE id = 3 RETURNING *;-- error: reference remains
2034+
ERROR: update or delete on table "part1_self_fk" violates foreign key constraint "parted_self_fk_id_abc_fkey1" on table "parted_self_fk"
2035+
DETAIL: Key (id)=(3) is still referenced from table "parted_self_fk".
20002036
ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk;
20012037
ALTER TABLE parted_self_fk ATTACH PARTITION part2_self_fk FOR VALUES FROM (10) TO (20);
2002-
SELECT cr.relname, co.conname, co.contype, co.convalidated,
2038+
ALTER TABLE parted_self_fk DETACH PARTITION part3_self_fk;
2039+
ALTER TABLE parted_self_fk ATTACH PARTITION part3_self_fk FOR VALUES FROM (30) TO (40);
2040+
ALTER TABLE part3_self_fk DETACH PARTITION part33_self_fk;
2041+
ALTER TABLE part3_self_fk ATTACH PARTITION part33_self_fk FOR VALUES FROM (30) TO (40);
2042+
SELECT cr.relname, co.conname, co.convalidated,
20032043
p.conname AS conparent, p.convalidated, cf.relname AS foreignrel
20042044
FROM pg_constraint co
20052045
JOIN pg_class cr ON cr.oid = co.conrelid
20062046
LEFT JOIN pg_class cf ON cf.oid = co.confrelid
20072047
LEFT JOIN pg_constraint p ON p.oid = co.conparentid
2008-
WHEREcr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk'))
2009-
ORDER BY co.contype,cr.relname, co.conname, p.conname;
2010-
relname |conname | contype | convalidated | conparent | convalidated | foreignrel
2011-
----------------+----------------------------+---------+--------------+----------------------------+--------------+----------------
2012-
part1_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2013-
part2_self_fk | parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2014-
part32_self_fk| parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2015-
part33_self_fk | parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2016-
part3_self_fk| parted_self_fk_id_abc_fkey| f| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2017-
parted_self_fk| parted_self_fk_id_abc_fkey| f| t | | | parted_self_fk
2018-
part1_self_fk | part1_self_fk_pkey| p| t| parted_self_fk_pkey| t|
2019-
part2_self_fk | part2_self_fk_pkey | p |t |parted_self_fk_pkey| t |
2020-
part32_self_fk |part32_self_fk_pkey | p |t |part3_self_fk_pkey| t |
2021-
part33_self_fk |part33_self_fk_pkey | p |t |part3_self_fk_pkey| t |
2022-
part3_self_fk | part3_self_fk_pkey | p |t |parted_self_fk_pkey| t |
2023-
parted_self_fk |parted_self_fk_pkey | p |t | | |
2024-
(12 rows)
2048+
WHEREco.contype = 'f' AND
2049+
cr.oid IN (SELECT relid FROM pg_partition_tree('parted_self_fk'))
2050+
ORDER BY cr.relname, co.conname, p.conname;
2051+
relname | conname | convalidated | conparent | convalidated | foreignrel
2052+
----------------+-----------------------------+--------------+-----------------------------+--------------+----------------
2053+
part1_self_fk | parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2054+
part2_self_fk| parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2055+
part32_self_fk | parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2056+
part33_self_fk| parted_self_fk_id_abc_fkey| t | parted_self_fk_id_abc_fkey | t | parted_self_fk
2057+
part3_self_fk| parted_self_fk_id_abc_fkey| t |parted_self_fk_id_abc_fkey | t | parted_self_fk
2058+
parted_self_fk | parted_self_fk_id_abc_fkey | t | | | parted_self_fk
2059+
parted_self_fk | parted_self_fk_id_abc_fkey1 |t |parted_self_fk_id_abc_fkey| t |part1_self_fk
2060+
parted_self_fk |parted_self_fk_id_abc_fkey2 |t |parted_self_fk_id_abc_fkey| t |part2_self_fk
2061+
parted_self_fk |parted_self_fk_id_abc_fkey3 |t |parted_self_fk_id_abc_fkey| t |part3_self_fk
2062+
parted_self_fk | parted_self_fk_id_abc_fkey4 |t |parted_self_fk_id_abc_fkey3| t |part32_self_fk
2063+
parted_self_fk |parted_self_fk_id_abc_fkey5 |t |parted_self_fk_id_abc_fkey3 | t| part33_self_fk
2064+
(11 rows)
20252065

20262066
-- Leave this table around, for pg_upgrade/pg_dump tests
20272067
-- Test creating a constraint at the parent that already exists in partitions.

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2713,23 +2713,27 @@ select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
27132713
tgfoid::regproc, tgenabled
27142714
from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass)
27152715
order by tgrelid::regclass::text, tgfoid;
2716-
tgrelid | tgname | tgfoid | tgenabled
2717-
---------+-------------------------+---------------------+-----------
2718-
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O
2719-
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O
2720-
(2 rows)
2716+
tgrelid | tgname | tgfoid | tgenabled
2717+
---------+-------------------------+------------------------+-----------
2718+
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O
2719+
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O
2720+
child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O
2721+
child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O
2722+
(4 rows)
27212723

27222724
-- Before v15, this has no effect because parent has no triggers:
27232725
alter table parent disable trigger all;
27242726
select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
27252727
tgfoid::regproc, tgenabled
27262728
from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass)
27272729
order by tgrelid::regclass::text, tgfoid;
2728-
tgrelid | tgname | tgfoid | tgenabled
2729-
---------+-------------------------+---------------------+-----------
2730-
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O
2731-
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O
2732-
(2 rows)
2730+
tgrelid | tgname | tgfoid | tgenabled
2731+
---------+-------------------------+------------------------+-----------
2732+
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O
2733+
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O
2734+
child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O
2735+
child1 | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O
2736+
(4 rows)
27332737

27342738
drop table parent, child1;
27352739
-- Verify that firing state propagates correctly on creation, too

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

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,29 +1438,55 @@ CREATE TABLE part33_self_fk (
14381438
);
14391439
ALTERTABLE part3_self_fk ATTACH PARTITION part33_self_fk FORVALUESFROM (30) TO (40);
14401440

1441-
SELECTcr.relname,co.conname,co.contype,co.convalidated,
1441+
-- verify that this constraint works
1442+
INSERT INTO parted_self_fkVALUES (1,NULL), (2,NULL), (3,NULL);
1443+
INSERT INTO parted_self_fkVALUES (10,1), (11,2), (12,3) RETURNING tableoid::regclass;
1444+
1445+
INSERT INTO parted_self_fkVALUES (4,5);-- error: referenced doesn't exist
1446+
DELETEFROM parted_self_fkWHERE id=1 RETURNING*;-- error: reference remains
1447+
1448+
SELECTcr.relname,co.conname,co.convalidated,
14421449
p.connameAS conparent,p.convalidated,cf.relnameAS foreignrel
14431450
FROM pg_constraint co
14441451
JOIN pg_class crONcr.oid=co.conrelid
14451452
LEFT JOIN pg_class cfONcf.oid=co.confrelid
14461453
LEFT JOIN pg_constraint pONp.oid=co.conparentid
1447-
WHEREcr.oidIN (SELECT relidFROM pg_partition_tree('parted_self_fk'))
1448-
ORDER BYco.contype,cr.relname,co.conname,p.conname;
1454+
WHEREco.contype='f'AND
1455+
cr.oidIN (SELECT relidFROM pg_partition_tree('parted_self_fk'))
1456+
ORDER BYcr.relname,co.conname,p.conname;
14491457

14501458
-- detach and re-attach multiple times just to ensure everything is kosher
14511459
ALTERTABLE parted_self_fk DETACH PARTITION part2_self_fk;
1460+
1461+
\d+ part2_self_fk
1462+
1463+
INSERT INTO part2_self_fkVALUES (16,9);-- good, but it'll prevent the attach below
1464+
ALTERTABLE parted_self_fk ATTACH PARTITION part2_self_fk FORVALUESFROM (10) TO (20);
1465+
1466+
DELETEFROM part2_self_fkWHERE id=16;
14521467
ALTERTABLE parted_self_fk ATTACH PARTITION part2_self_fk FORVALUESFROM (10) TO (20);
1468+
1469+
INSERT INTO parted_self_fkVALUES (16,9);-- error: referenced doesn't exist
1470+
DELETEFROM parted_self_fkWHERE id=3 RETURNING*;-- error: reference remains
1471+
14531472
ALTERTABLE parted_self_fk DETACH PARTITION part2_self_fk;
14541473
ALTERTABLE parted_self_fk ATTACH PARTITION part2_self_fk FORVALUESFROM (10) TO (20);
14551474

1456-
SELECTcr.relname,co.conname,co.contype,co.convalidated,
1475+
ALTERTABLE parted_self_fk DETACH PARTITION part3_self_fk;
1476+
ALTERTABLE parted_self_fk ATTACH PARTITION part3_self_fk FORVALUESFROM (30) TO (40);
1477+
1478+
ALTERTABLE part3_self_fk DETACH PARTITION part33_self_fk;
1479+
ALTERTABLE part3_self_fk ATTACH PARTITION part33_self_fk FORVALUESFROM (30) TO (40);
1480+
1481+
SELECTcr.relname,co.conname,co.convalidated,
14571482
p.connameAS conparent,p.convalidated,cf.relnameAS foreignrel
14581483
FROM pg_constraint co
14591484
JOIN pg_class crONcr.oid=co.conrelid
14601485
LEFT JOIN pg_class cfONcf.oid=co.confrelid
14611486
LEFT JOIN pg_constraint pONp.oid=co.conparentid
1462-
WHEREcr.oidIN (SELECT relidFROM pg_partition_tree('parted_self_fk'))
1463-
ORDER BYco.contype,cr.relname,co.conname,p.conname;
1487+
WHEREco.contype='f'AND
1488+
cr.oidIN (SELECT relidFROM pg_partition_tree('parted_self_fk'))
1489+
ORDER BYcr.relname,co.conname,p.conname;
14641490

14651491
-- Leave this table around, for pg_upgrade/pg_dump tests
14661492

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp