diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 446b54b9ffa..698b493fc40 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -783,6 +783,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; @@ -797,10 +803,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 887c19c3eff..e010586dd67 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7243,6 +7243,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; @@ -7586,6 +7587,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. */ @@ -7616,7 +7623,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, NULL, true, /* islocal */ 0, /* inhcount */ - true, /* isnoinherit */ + connoinherit, /* conNoInherit */ false); /* is_internal */ ObjectAddressSet(address, ConstraintRelationId, constrOid); @@ -7937,7 +7944,7 @@ CloneFkReferencing(Relation pg_constraint, Relation parentRel, } systable_endscan(scan); - heap_close(trigrel, RowExclusiveLock); + table_close(trigrel, RowExclusiveLock); ConstraintSetParentConstraint(fk->conoid, parentConstrOid); CommandCounterIncrement(); @@ -9131,6 +9138,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) @@ -9171,6 +9179,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 @@ -9179,7 +9188,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; @@ -9223,6 +9232,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) + { + table_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 76040a76011..bf2c91d9f0e 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1945,7 +1945,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 9ed1166c666..c8d1214d02c 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1392,6 +1392,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