diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 5720c652b2c..c0ae92c0052 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -792,6 +792,12 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) constrForm = (Form_pg_constraint) GETSTRUCT(newtup); if (OidIsValid(parentConstrId)) { + /* don't allow setting parent for a constraint that already has one */ + Assert(constrForm->coninhcount == 0); + if (constrForm->conparentid != InvalidOid) + elog(ERROR, "constraint %u already has a parent constraint", + childConstrId); + constrForm->conislocal = false; constrForm->coninhcount++; constrForm->conparentid = parentConstrId; @@ -806,10 +812,12 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) else { constrForm->coninhcount--; - if (constrForm->coninhcount <= 0) - constrForm->conislocal = true; + constrForm->conislocal = true; constrForm->conparentid = InvalidOid; + /* Make sure there's no further inheritance. */ + Assert(constrForm->coninhcount == 0); + deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId, ConstraintRelationId, DEPENDENCY_INTERNAL_AUTO); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 747acbd2b91..910e5deaa3f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7336,6 +7336,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Oid pfeqoperators[INDEX_MAX_KEYS]; Oid ppeqoperators[INDEX_MAX_KEYS]; Oid ffeqoperators[INDEX_MAX_KEYS]; + bool connoinherit; int i; int numfks, numpks; @@ -7679,6 +7680,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, ffeqoperators[i] = ffeqop; } + /* + * FKs always inherit for partitioned tables, and never for legacy + * inheritance. + */ + connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE; + /* * Record the FK constraint in pg_constraint. */ @@ -7710,7 +7717,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, NULL, true, /* islocal */ 0, /* inhcount */ - true, /* isnoinherit */ + connoinherit, /* conNoInherit */ false); /* is_internal */ ObjectAddressSet(address, ConstraintRelationId, constrOid); @@ -9230,6 +9237,7 @@ ATExecDropConstraint(Relation rel, const char *constrName, HeapTuple tuple; bool found = false; bool is_no_inherit_constraint = false; + char contype; /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) @@ -9270,6 +9278,7 @@ ATExecDropConstraint(Relation rel, const char *constrName, constrName, RelationGetRelationName(rel)))); is_no_inherit_constraint = con->connoinherit; + contype = con->contype; /* * If it's a foreign-key constraint, we'd better lock the referenced @@ -9278,7 +9287,7 @@ ATExecDropConstraint(Relation rel, const char *constrName, * that has unfired events). But we can/must skip that in the * self-referential case. */ - if (con->contype == CONSTRAINT_FOREIGN && + if (contype == CONSTRAINT_FOREIGN && con->confrelid != RelationGetRelid(rel)) { Relation frel; @@ -9322,6 +9331,17 @@ ATExecDropConstraint(Relation rel, const char *constrName, } } + /* + * For partitioned tables, non-CHECK inherited constraints are dropped via + * the dependency mechanism, so we're done here. + */ + if (contype != CONSTRAINT_CHECK && + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + heap_close(conrel, RowExclusiveLock); + return; + } + /* * Propagate to children as appropriate. Unlike most other ALTER * routines, we have to do this one level of recursion at a time; we can't diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 4fd23413882..8c7188828b5 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1884,7 +1884,23 @@ DETAIL: Key (a)=(2) is not present in table "pkey". delete from fkpart1.pkey where a = 1; ERROR: update or delete on table "pkey" violates foreign key constraint "fk_part_a_fkey" on table "fk_part_1" DETAIL: Key (a)=(1) is still referenced from table "fk_part_1". +-- verify that attaching and detaching partitions manipulates the inheritance +-- properties of their FK constraints correctly +create schema fkpart2 + create table pkey (a int primary key) + create table fk_part (a int, constraint fkey foreign key (a) references fkpart2.pkey) partition by list (a) + create table fk_part_1 partition of fkpart2.fk_part for values in (1) partition by list (a) + create table fk_part_1_1 (a int, constraint my_fkey foreign key (a) references fkpart2.pkey); +alter table fkpart2.fk_part_1 attach partition fkpart2.fk_part_1_1 for values in (1); +alter table fkpart2.fk_part_1 drop constraint fkey; -- should fail +ERROR: cannot drop inherited constraint "fkey" of relation "fk_part_1" +alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- should fail +ERROR: cannot drop inherited constraint "my_fkey" of relation "fk_part_1_1" +alter table fkpart2.fk_part detach partition fkpart2.fk_part_1; +alter table fkpart2.fk_part_1 drop constraint fkey; -- ok +alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- doesn't exist +ERROR: constraint "my_fkey" of relation "fk_part_1_1" does not exist \set VERBOSITY terse \\ -- suppress cascade details -drop schema fkpart0, fkpart1 cascade; -NOTICE: drop cascades to 5 other objects +drop schema fkpart0, fkpart1, fkpart2 cascade; +NOTICE: drop cascades to 8 other objects \set VERBOSITY default diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index dd0be01c774..724f631881c 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1341,6 +1341,20 @@ create table fkpart1.fk_part_1_2 partition of fkpart1.fk_part_1 for values in (2 insert into fkpart1.fk_part_1 values (2); -- should fail delete from fkpart1.pkey where a = 1; +-- verify that attaching and detaching partitions manipulates the inheritance +-- properties of their FK constraints correctly +create schema fkpart2 + create table pkey (a int primary key) + create table fk_part (a int, constraint fkey foreign key (a) references fkpart2.pkey) partition by list (a) + create table fk_part_1 partition of fkpart2.fk_part for values in (1) partition by list (a) + create table fk_part_1_1 (a int, constraint my_fkey foreign key (a) references fkpart2.pkey); +alter table fkpart2.fk_part_1 attach partition fkpart2.fk_part_1_1 for values in (1); +alter table fkpart2.fk_part_1 drop constraint fkey; -- should fail +alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- should fail +alter table fkpart2.fk_part detach partition fkpart2.fk_part_1; +alter table fkpart2.fk_part_1 drop constraint fkey; -- ok +alter table fkpart2.fk_part_1_1 drop constraint my_fkey; -- doesn't exist + \set VERBOSITY terse \\ -- suppress cascade details -drop schema fkpart0, fkpart1 cascade; +drop schema fkpart0, fkpart1, fkpart2 cascade; \set VERBOSITY default