Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 1ad5210

Browse files
committed
Fix droppability of constraints upon partition detach
We were failing to set conislocal correctly for constraints in partitions after partition detach, leading to those constraints becoming undroppable. Fix by setting the flag correctly. Existing databases might contain constraints with the conislocal wrongly set to false, for partitions that were detached; this situation should be fixable by applying an UPDATE on pg_constraint to set conislocal true. This problem should otherwise be innocuous and should disappear across a dump/restore or pg_upgrade. Secondarily, when constraint drop was attempted in a partitioned table, ATExecDropConstraint would try to recurse to partitions after doing performDeletion() of the constraint in the partitioned table itself; but since the constraint in the partitions are dropped by the initial call of performDeletion() (because of following dependencies), the recursion step would fail since it would not find the constraint, causing the whole operation to fail. Fix by preventing recursion. Reported-by: Amit Langote Diagnosed-by: Amit Langote Author: Amit Langote, Álvaro Herrera Discussion: https://postgr.es/m/f2b8ead5-4131-d5a8-8016-2ea0a31250af@lab.ntt.co.jp
1 parent 27d6bc6 commit 1ad5210

File tree

4 files changed

+65
-7
lines changed

4 files changed

+65
-7
lines changed

src/backend/catalog/pg_constraint.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,12 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId)
792792
constrForm = (Form_pg_constraint) GETSTRUCT(newtup);
793793
if (OidIsValid(parentConstrId))
794794
{
795+
/* don't allow setting parent for a constraint that already has one */
796+
Assert(constrForm->coninhcount == 0);
797+
if (constrForm->conparentid != InvalidOid)
798+
elog(ERROR, "constraint %u already has a parent constraint",
799+
childConstrId);
800+
795801
constrForm->conislocal = false;
796802
constrForm->coninhcount++;
797803
constrForm->conparentid = parentConstrId;
@@ -806,10 +812,12 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId)
806812
else
807813
{
808814
constrForm->coninhcount--;
809-
if (constrForm->coninhcount <= 0)
810-
constrForm->conislocal = true;
815+
constrForm->conislocal = true;
811816
constrForm->conparentid = InvalidOid;
812817

818+
/* Make sure there's no further inheritance. */
819+
Assert(constrForm->coninhcount == 0);
820+
813821
deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId,
814822
ConstraintRelationId,
815823
DEPENDENCY_INTERNAL_AUTO);

src/backend/commands/tablecmds.c

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7336,6 +7336,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
73367336
Oid pfeqoperators[INDEX_MAX_KEYS];
73377337
Oid ppeqoperators[INDEX_MAX_KEYS];
73387338
Oid ffeqoperators[INDEX_MAX_KEYS];
7339+
bool connoinherit;
73397340
int i;
73407341
int numfks,
73417342
numpks;
@@ -7679,6 +7680,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
76797680
ffeqoperators[i] = ffeqop;
76807681
}
76817682

7683+
/*
7684+
* FKs always inherit for partitioned tables, and never for legacy
7685+
* inheritance.
7686+
*/
7687+
connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE;
7688+
76827689
/*
76837690
* Record the FK constraint in pg_constraint.
76847691
*/
@@ -7710,7 +7717,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
77107717
NULL,
77117718
true, /* islocal */
77127719
0, /* inhcount */
7713-
true, /* isnoinherit */
7720+
connoinherit, /* conNoInherit */
77147721
false); /* is_internal */
77157722
ObjectAddressSet(address, ConstraintRelationId, constrOid);
77167723

@@ -9230,6 +9237,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
92309237
HeapTuple tuple;
92319238
bool found = false;
92329239
bool is_no_inherit_constraint = false;
9240+
char contype;
92339241

92349242
/* At top level, permission check was done in ATPrepCmd, else do it */
92359243
if (recursing)
@@ -9270,6 +9278,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
92709278
constrName, RelationGetRelationName(rel))));
92719279

92729280
is_no_inherit_constraint = con->connoinherit;
9281+
contype = con->contype;
92739282

92749283
/*
92759284
* If it's a foreign-key constraint, we'd better lock the referenced
@@ -9278,7 +9287,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
92789287
* that has unfired events). But we can/must skip that in the
92799288
* self-referential case.
92809289
*/
9281-
if (con->contype == CONSTRAINT_FOREIGN &&
9290+
if (contype == CONSTRAINT_FOREIGN &&
92829291
con->confrelid != RelationGetRelid(rel))
92839292
{
92849293
Relation frel;
@@ -9322,6 +9331,17 @@ ATExecDropConstraint(Relation rel, const char *constrName,
93229331
}
93239332
}
93249333

9334+
/*
9335+
* For partitioned tables, non-CHECK inherited constraints are dropped via
9336+
* the dependency mechanism, so we're done here.
9337+
*/
9338+
if (contype != CONSTRAINT_CHECK &&
9339+
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9340+
{
9341+
heap_close(conrel, RowExclusiveLock);
9342+
return;
9343+
}
9344+
93259345
/*
93269346
* Propagate to children as appropriate. Unlike most other ALTER
93279347
* routines, we have to do this one level of recursion at a time; we can't

src/test/regress/expected/foreign_key.out

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,7 +1884,23 @@ DETAIL: Key (a)=(2) is not present in table "pkey".
18841884
delete from fkpart1.pkey where a = 1;
18851885
ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part_1"
18861886
DETAIL: Key (a)=(1) is still referenced from table "fk_part_1".
1887+
-- verify that attaching and detaching partitions manipulates the inheritance
1888+
-- properties of their FK constraints correctly
1889+
create schema fkpart2
1890+
create table pkey (a int primary key)
1891+
create table fk_part (a int, constraint fkey foreign key (a) references fkpart2.pkey) partition by list (a)
1892+
create table fk_part_1 partition of fkpart2.fk_part for values in (1) partition by list (a)
1893+
create table fk_part_1_1 (a int, constraint my_fkey foreign key (a) references fkpart2.pkey);
1894+
alter table fkpart2.fk_part_1 attach partition fkpart2.fk_part_1_1 for values in (1);
1895+
alter table fkpart2.fk_part_1 drop constraint fkey; -- should fail
1896+
ERROR: cannot drop inherited constraint "fkey" of relation "fk_part_1"
1897+
alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- should fail
1898+
ERROR: cannot drop inherited constraint "my_fkey" of relation "fk_part_1_1"
1899+
alter table fkpart2.fk_part detach partition fkpart2.fk_part_1;
1900+
alter table fkpart2.fk_part_1 drop constraint fkey; -- ok
1901+
alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- doesn't exist
1902+
ERROR: constraint "my_fkey" of relation "fk_part_1_1" does not exist
18871903
\set VERBOSITY terse \\ -- suppress cascade details
1888-
drop schema fkpart0, fkpart1 cascade;
1889-
NOTICE: drop cascades to 5 other objects
1904+
drop schema fkpart0, fkpart1, fkpart2 cascade;
1905+
NOTICE: drop cascades to 8 other objects
18901906
\set VERBOSITY default

src/test/regress/sql/foreign_key.sql

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,20 @@ create table fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 for values in (2
13411341
insert into fkpart1.fk_part_1 values (2); -- should fail
13421342
delete from fkpart1.pkey where a = 1;
13431343

1344+
-- verify that attaching and detaching partitions manipulates the inheritance
1345+
-- properties of their FK constraints correctly
1346+
create schema fkpart2
1347+
create table pkey (a int primary key)
1348+
create table fk_part (a int, constraint fkey foreign key (a) references fkpart2.pkey) partition by list (a)
1349+
create table fk_part_1 partition of fkpart2.fk_part for values in (1) partition by list (a)
1350+
create table fk_part_1_1 (a int, constraint my_fkey foreign key (a) references fkpart2.pkey);
1351+
alter table fkpart2.fk_part_1 attach partition fkpart2.fk_part_1_1 for values in (1);
1352+
alter table fkpart2.fk_part_1 drop constraint fkey; -- should fail
1353+
alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- should fail
1354+
alter table fkpart2.fk_part detach partition fkpart2.fk_part_1;
1355+
alter table fkpart2.fk_part_1 drop constraint fkey; -- ok
1356+
alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- doesn't exist
1357+
13441358
\set VERBOSITY terse \\ -- suppress cascade details
1345-
drop schema fkpart0, fkpart1 cascade;
1359+
drop schema fkpart0, fkpart1, fkpart2 cascade;
13461360
\set VERBOSITY default

0 commit comments

Comments
 (0)