Skip to content

Commit 4dff893

Browse files
committed
Fix creation of duplicate foreign keys on partitions
When creating a foreign key in a partitioned table, if some partitions already have equivalent constraints, we wastefully create duplicates of the constraints instead of attaching to the existing ones. That's inconsistent with the de-duplication that is applied when a table is attached as a partition. To fix, reuse the FK-cloning code instead of having a separate code path. Backpatch to Postgres 11. This is a subtle behavior change, but surely a welcome one since there's no use in having duplicate foreign keys. Discovered by Álvaro Herrera while thinking about a different problem reported by Jesper Pedersen (bug #15587). Author: Álvaro Herrera Discussion: https://postgr.es/m/201901151935.zfadrzvyof4k@alvherre.pgsql
1 parent fca6cab commit 4dff893

File tree

3 files changed

+150
-10
lines changed

3 files changed

+150
-10
lines changed

src/backend/commands/tablecmds.c

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7751,30 +7751,49 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
77517751
if (recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
77527752
{
77537753
PartitionDesc partdesc;
7754+
Relation pg_constraint;
7755+
List *cloned = NIL;
7756+
ListCell *cell;
77547757

7758+
pg_constraint = heap_open(ConstraintRelationId, RowExclusiveLock);
77557759
partdesc = RelationGetPartitionDesc(rel);
77567760

77577761
for (i = 0; i < partdesc->nparts; i++)
77587762
{
77597763
Oid partitionId = partdesc->oids[i];
77607764
Relation partition = heap_open(partitionId, lockmode);
7761-
AlteredTableInfo *childtab;
7762-
ObjectAddress childAddr;
77637765

77647766
CheckTableNotInUse(partition, "ALTER TABLE");
77657767

7766-
/* Find or create work queue entry for this table */
7768+
CloneFkReferencing(pg_constraint, rel, partition,
7769+
list_make1_oid(constrOid),
7770+
&cloned);
7771+
7772+
heap_close(partition, NoLock);
7773+
}
7774+
heap_close(pg_constraint, RowExclusiveLock);
7775+
7776+
foreach(cell, cloned)
7777+
{
7778+
ClonedConstraint *cc = (ClonedConstraint *) lfirst(cell);
7779+
Relation partition = heap_open(cc->relid, lockmode);
7780+
AlteredTableInfo *childtab;
7781+
NewConstraint *newcon;
7782+
7783+
/* Find or create work queue entry for this partition */
77677784
childtab = ATGetQueueEntry(wqueue, partition);
77687785

7769-
childAddr =
7770-
ATAddForeignKeyConstraint(wqueue, childtab, partition,
7771-
fkconstraint, constrOid,
7772-
recurse, true, lockmode);
7786+
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
7787+
newcon->name = cc->constraint->conname;
7788+
newcon->contype = CONSTR_FOREIGN;
7789+
newcon->refrelid = cc->refrelid;
7790+
newcon->refindid = cc->conindid;
7791+
newcon->conid = cc->conid;
7792+
newcon->qual = (Node *) fkconstraint;
77737793

7774-
/* Record this constraint as dependent on the parent one */
7775-
recordDependencyOn(&childAddr, &address, DEPENDENCY_INTERNAL_AUTO);
7794+
childtab->constraints = lappend(childtab->constraints, newcon);
77767795

7777-
heap_close(partition, NoLock);
7796+
heap_close(partition, lockmode);
77787797
}
77797798
}
77807799

src/test/regress/expected/foreign_key.out

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,3 +1781,86 @@ INSERT INTO fk_notpartitioned_pk VALUES (1600, 601), (1600, 1601);
17811781
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2
17821782
FOR VALUES IN (1600);
17831783
-- leave these tables around intentionally
1784+
-- Test creating a constraint at the parent that already exists in partitions.
1785+
-- There should be no duplicated constraints, and attempts to drop the
1786+
-- constraint in partitions should raise appropriate errors.
1787+
create schema fkpart0
1788+
create table pkey (a int primary key)
1789+
create table fk_part (a int) partition by list (a)
1790+
create table fk_part_1 partition of fk_part
1791+
(foreign key (a) references fkpart0.pkey) for values in (1)
1792+
create table fk_part_23 partition of fk_part
1793+
(foreign key (a) references fkpart0.pkey) for values in (2, 3)
1794+
partition by list (a)
1795+
create table fk_part_23_2 partition of fk_part_23 for values in (2);
1796+
alter table fkpart0.fk_part add foreign key (a) references fkpart0.pkey;
1797+
\d fkpart0.fk_part_1 \\ -- should have only one FK
1798+
Table "fkpart0.fk_part_1"
1799+
Column | Type | Collation | Nullable | Default
1800+
--------+---------+-----------+----------+---------
1801+
a | integer | | |
1802+
Partition of: fkpart0.fk_part FOR VALUES IN (1)
1803+
Foreign-key constraints:
1804+
"fk_part_1_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
1805+
1806+
alter table fkpart0.fk_part_1 drop constraint fk_part_1_a_fkey;
1807+
ERROR: cannot drop inherited constraint "fk_part_1_a_fkey" of relation "fk_part_1"
1808+
\d fkpart0.fk_part_23 \\ -- should have only one FK
1809+
Table "fkpart0.fk_part_23"
1810+
Column | Type | Collation | Nullable | Default
1811+
--------+---------+-----------+----------+---------
1812+
a | integer | | |
1813+
Partition of: fkpart0.fk_part FOR VALUES IN (2, 3)
1814+
Partition key: LIST (a)
1815+
Foreign-key constraints:
1816+
"fk_part_23_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
1817+
Number of partitions: 1 (Use \d+ to list them.)
1818+
1819+
\d fkpart0.fk_part_23_2 \\ -- should have only one FK
1820+
Table "fkpart0.fk_part_23_2"
1821+
Column | Type | Collation | Nullable | Default
1822+
--------+---------+-----------+----------+---------
1823+
a | integer | | |
1824+
Partition of: fkpart0.fk_part_23 FOR VALUES IN (2)
1825+
Foreign-key constraints:
1826+
"fk_part_23_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
1827+
1828+
alter table fkpart0.fk_part_23 drop constraint fk_part_23_a_fkey;
1829+
ERROR: cannot drop inherited constraint "fk_part_23_a_fkey" of relation "fk_part_23"
1830+
alter table fkpart0.fk_part_23_2 drop constraint fk_part_23_a_fkey;
1831+
ERROR: cannot drop inherited constraint "fk_part_23_a_fkey" of relation "fk_part_23_2"
1832+
create table fkpart0.fk_part_4 partition of fkpart0.fk_part for values in (4);
1833+
\d fkpart0.fk_part_4
1834+
Table "fkpart0.fk_part_4"
1835+
Column | Type | Collation | Nullable | Default
1836+
--------+---------+-----------+----------+---------
1837+
a | integer | | |
1838+
Partition of: fkpart0.fk_part FOR VALUES IN (4)
1839+
Foreign-key constraints:
1840+
"fk_part_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
1841+
1842+
alter table fkpart0.fk_part_4 drop constraint fk_part_a_fkey;
1843+
ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_4"
1844+
create table fkpart0.fk_part_56 partition of fkpart0.fk_part
1845+
for values in (5,6) partition by list (a);
1846+
create table fkpart0.fk_part_56_5 partition of fkpart0.fk_part_56
1847+
for values in (5);
1848+
\d fkpart0.fk_part_56
1849+
Table "fkpart0.fk_part_56"
1850+
Column | Type | Collation | Nullable | Default
1851+
--------+---------+-----------+----------+---------
1852+
a | integer | | |
1853+
Partition of: fkpart0.fk_part FOR VALUES IN (5, 6)
1854+
Partition key: LIST (a)
1855+
Foreign-key constraints:
1856+
"fk_part_a_fkey" FOREIGN KEY (a) REFERENCES fkpart0.pkey(a)
1857+
Number of partitions: 1 (Use \d+ to list them.)
1858+
1859+
alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey;
1860+
ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56"
1861+
alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey;
1862+
ERROR: cannot drop inherited constraint "fk_part_a_fkey" of relation "fk_part_56_5"
1863+
\set VERBOSITY terse \\ -- suppress cascade details
1864+
drop schema fkpart0 cascade;
1865+
NOTICE: drop cascades to 2 other objects
1866+
\set VERBOSITY default

src/test/regress/sql/foreign_key.sql

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,3 +1289,41 @@ ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2
12891289
FOR VALUES IN (1600);
12901290

12911291
-- leave these tables around intentionally
1292+
1293+
-- Test creating a constraint at the parent that already exists in partitions.
1294+
-- There should be no duplicated constraints, and attempts to drop the
1295+
-- constraint in partitions should raise appropriate errors.
1296+
create schema fkpart0
1297+
create table pkey (a int primary key)
1298+
create table fk_part (a int) partition by list (a)
1299+
create table fk_part_1 partition of fk_part
1300+
(foreign key (a) references fkpart0.pkey) for values in (1)
1301+
create table fk_part_23 partition of fk_part
1302+
(foreign key (a) references fkpart0.pkey) for values in (2, 3)
1303+
partition by list (a)
1304+
create table fk_part_23_2 partition of fk_part_23 for values in (2);
1305+
1306+
alter table fkpart0.fk_part add foreign key (a) references fkpart0.pkey;
1307+
\d fkpart0.fk_part_1 \\ -- should have only one FK
1308+
alter table fkpart0.fk_part_1 drop constraint fk_part_1_a_fkey;
1309+
1310+
\d fkpart0.fk_part_23 \\ -- should have only one FK
1311+
\d fkpart0.fk_part_23_2 \\ -- should have only one FK
1312+
alter table fkpart0.fk_part_23 drop constraint fk_part_23_a_fkey;
1313+
alter table fkpart0.fk_part_23_2 drop constraint fk_part_23_a_fkey;
1314+
1315+
create table fkpart0.fk_part_4 partition of fkpart0.fk_part for values in (4);
1316+
\d fkpart0.fk_part_4
1317+
alter table fkpart0.fk_part_4 drop constraint fk_part_a_fkey;
1318+
1319+
create table fkpart0.fk_part_56 partition of fkpart0.fk_part
1320+
for values in (5,6) partition by list (a);
1321+
create table fkpart0.fk_part_56_5 partition of fkpart0.fk_part_56
1322+
for values in (5);
1323+
\d fkpart0.fk_part_56
1324+
alter table fkpart0.fk_part_56 drop constraint fk_part_a_fkey;
1325+
alter table fkpart0.fk_part_56_5 drop constraint fk_part_a_fkey;
1326+
1327+
\set VERBOSITY terse \\ -- suppress cascade details
1328+
drop schema fkpart0 cascade;
1329+
\set VERBOSITY default

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy