Hi, I noticed a couple of problems with foreign keys on partitioned tables.
1. Foreign keys of partitions stop working correctly after being detached from the parent table create table pk (a int primary key); create table p (a int) partition by list (a); create table p1 partition of p for values in (1) partition by list (a); create table p11 partition of p1 for values in (1); alter table p add foreign key (a) references pk (a); -- these things work correctly insert into p values (1); ERROR: insert or update on table "p11" violates foreign key constraint "p_a_fkey" DETAIL: Key (a)=(1) is not present in table "pk". insert into pk values (1); insert into p values (1); delete from pk where a = 1; ERROR: update or delete on table "pk" violates foreign key constraint "p_a_fkey" on table "p" DETAIL: Key (a)=(1) is still referenced from table "p". -- detach p1, which preserves the foreign key key alter table p detach partition p1; create table p12 partition of p1 for values in (2); -- this part of the foreign key on p1 still works insert into p1 values (2); ERROR: insert or update on table "p12" violates foreign key constraint "p_a_fkey" DETAIL: Key (a)=(2) is not present in table "pk". -- but this seems wrong delete from pk where a = 1; DELETE 1 -- because select * from p1; a ─── 1 (1 row) This happens because the action triggers defined on the PK relation (pk) refers to p as the referencing relation. On detaching p1 from p, p1's data is no longer accessible to that trigger. To fix this problem, we need create action triggers on PK relation that refer to p1 when it's detached (unless such triggers already exist which might be true in some cases). Attached patch 0001 shows this approach. 2. Foreign keys of a partition cannot be dropped in some cases after detaching it from the parent. create table p (a int references pk) partition by list (a); create table p1 partition of p for values in (1) partition by list (a); create table p11 partition of p1 for values in (1); alter table p detach partition p1; -- p1's foreign key is no longer inherited, so should be able to drop it alter table p1 drop constraint p_a_fkey ; ERROR: constraint "p_a_fkey" of relation "p11" does not exist This happens because by the time ATExecDropConstraint tries to recursively drop the p11's inherited foreign key constraint (which is what normally happens for inherited constraints), the latter has already been dropped by dependency management. I think the foreign key inheritance related code doesn't need to add dependencies for something that inheritance recursion can take of and I can't think of any other reason to have such dependencies around. I thought maybe they're needed for pg_dump to work correctly, but apparently not so. Interestingly, the above problem doesn't occur if the constraint is added to partitions by inheritance recursion. create table p (a int) partition by list (a); create table p1 partition of p for values in (1) partition by list (a); create table p11 partition of p1 for values in (1); alter table p add foreign key (a) references pk (a); alter table p detach partition p1; alter table p1 drop constraint p_a_fkey ; ALTER TABLE Looking into it, that happens to work *accidentally*. ATExecDropInherit() doesn't try to recurse, which prevents the error in this case, because it finds that the constraint on p1 is marked NO INHERIT (non-inheritable), which is incorrect. The value of p1's constraint's connoinherit (in fact, other inheritance related properties too) is incorrect, because ATAddForeignKeyConstraint doesn't bother to set them correctly. This is what the inheritance properties of various copies of 'p_a_fkey' looks like in the catalog in this case: -- case 1: foreign key added to partitions recursively create table p (a int) partition by list (a); create table p1 partition of p for values in (1) partition by list (a); create table p11 partition of p1 for values in (1); alter table p add foreign key (a) references pk (a); select conname, conrelid::regclass, conislocal, coninhcount, connoinherit from pg_constraint where conname like 'p%fkey%'; conname │ conrelid │ conislocal │ coninhcount │ connoinherit ──────────┼──────────┼────────────┼─────────────┼────────────── p_a_fkey │ p │ t │ 0 │ t p_a_fkey │ p1 │ t │ 0 │ t p_a_fkey │ p11 │ t │ 0 │ t (3 rows) In this case, after detaching p1 from p, p1's foreign key's coninhcount turns to -1, which is not good. alter table p detach partition p1; select conname, conrelid::regclass, conislocal, coninhcount, connoinherit from pg_constraint where conname like 'p%fkey%'; conname │ conrelid │ conislocal │ coninhcount │ connoinherit ──────────┼──────────┼────────────┼─────────────┼────────────── p_a_fkey │ p │ t │ 0 │ t p_a_fkey │ p11 │ t │ 0 │ t p_a_fkey │ p1 │ t │ -1 │ t (3 rows) -- case 2: foreign keys cloned to partitions after adding partitions create table p (a int references pk) partition by list (a); create table p1 partition of p for values in (1) partition by list (a); create table p11 partition of p1 for values in (1); select conname, conrelid::regclass, conislocal, coninhcount, connoinherit from pg_constraint where conname like 'p%fkey%'; conname │ conrelid │ conislocal │ coninhcount │ connoinherit ──────────┼──────────┼────────────┼─────────────┼────────────── p_a_fkey │ p │ t │ 0 │ t p_a_fkey │ p1 │ f │ 1 │ f p_a_fkey │ p11 │ f │ 1 │ f (3 rows) Anyway, I propose we fix this by first getting rid of dependencies for foreign key constraints and instead rely on inheritance recursion for dropping the inherited constraints. Before we can do that, we'll need to consistently set the inheritance properties of foreign key constraints correctly, that is, teach ATAddForeignKeyConstraint what clone_fk_constraints already does correctly. See the attached patch 0002 for that. I'm also attaching versions of 0001 and 0002 that can be applied to PG 11. Thanks, Amit
From 86e18c2855ee9f8df19dd55dd024938ba2d41f78 Mon Sep 17 00:00:00 2001 From: amit <amitlangot...@gmail.com> Date: Wed, 9 Jan 2019 10:00:11 +0900 Subject: [PATCH v1 1/2] Ensure PK-side action triggers for partitions after being detached Detaching a partition from the parent whose foreign key(s) would've been duplicated in the partition makes the foreign key(s) stop working corretly. That's because PK-side action trigger for the foreign key would refer to the parent as the referencing relation and after the partition is detached it's data is no longer accessible via parent. So while the check triggers that remain even after being detached takes care of the one side of enforcing the foreign key of the detached partition, lack of corresponding PK-side action trigger to check detached partition's data means the other side doesn't work. To fix, add the action triggers on the PK relation that points to the detached partition when detaching. --- src/backend/catalog/pg_constraint.c | 3 +- src/backend/commands/tablecmds.c | 57 +++++++++++++++++++++++++++++++++++-- src/include/commands/tablecmds.h | 3 +- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index f4057a9f15..a379bec202 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -720,7 +720,8 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, fkconstraint->initdeferred = constrForm->condeferred; createForeignKeyTriggers(partRel, constrForm->confrelid, fkconstraint, - constrOid, constrForm->conindid, false); + constrOid, constrForm->conindid, false, + true); if (cloned) { diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 04b1098320..ae83deaf51 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7718,7 +7718,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * though the constraints also exist below. */ createForeignKeyTriggers(rel, RelationGetRelid(pkrel), fkconstraint, - constrOid, indexOid, !recursing); + constrOid, indexOid, !recursing, true); /* * Tell Phase 3 to check that the constraint is satisfied by existing @@ -8827,7 +8827,8 @@ createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid, */ void createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, - Oid constraintOid, Oid indexOid, bool create_action) + Oid constraintOid, Oid indexOid, bool create_action, + bool create_check) { /* * For the referenced side, create action triggers, if requested. (If the @@ -8843,7 +8844,7 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, * For the referencing side, create the check triggers. We only need * these on the partitions. */ - if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && create_check) createForeignKeyCheckTriggers(RelationGetRelid(rel), refRelOid, fkconstraint, constraintOid, indexOid); @@ -14813,13 +14814,63 @@ ATExecDetachPartition(Relation rel, RangeVar *name) { ForeignKeyCacheInfo *fk = lfirst(cell); HeapTuple contup; + Form_pg_constraint conform; + Constraint *fkconstraint; + Relation trigrel; + HeapTuple trigtup; + HeapScanDesc scan; + ScanKeyData key[3]; contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid)); if (!contup) elog(ERROR, "cache lookup failed for constraint %u", fk->conoid); + conform = (Form_pg_constraint) GETSTRUCT(contup); ConstraintSetParentConstraint(fk->conoid, InvalidOid); + /* + * We'll need to make the triggers on primary key relation to point + * to this relation as the FK relation. Currently, it points to + * the parent from which this relation is being detached. + */ + fkconstraint = makeNode(Constraint); + fkconstraint->conname = pstrdup(NameStr(conform->conname)); + fkconstraint->fk_upd_action = conform->confupdtype; + fkconstraint->fk_del_action = conform->confdeltype; + fkconstraint->deferrable = conform->condeferrable; + fkconstraint->initdeferred = conform->condeferred; + + /* First check if such a trigger doesn't already exist. */ + trigrel = heap_open(TriggerRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_trigger_tgrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(conform->confrelid)); + ScanKeyInit(&key[1], + Anum_pg_trigger_tgconstrrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(partRel))); + ScanKeyInit(&key[2], + Anum_pg_trigger_tgconstraint, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(fk->conoid)); + scan = heap_beginscan_catalog(trigrel, 3, key); + trigtup = heap_getnext(scan, ForwardScanDirection); + if (trigtup == NULL) + { + /* + * Nope, we didn't find such a trigger, so create one. + * + * As we already got the check triggers, no need to recreate them, + * so pass false for create_check. + */ + createForeignKeyTriggers(partRel, conform->confrelid, fkconstraint, + fk->conoid, conform->conindid, + true, false); + } + heap_endscan(scan); + heap_close(trigrel, AccessShareLock); + ReleaseSysCache(contup); } list_free_deep(fks); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 138de84e83..dd985a80b6 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -77,7 +77,8 @@ extern void check_of_type(HeapTuple typetuple); extern void createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, Oid constraintOid, - Oid indexOid, bool create_action); + Oid indexOid, bool create_action, + bool create_check); extern void register_on_commit_action(Oid relid, OnCommitAction action); extern void remove_on_commit_action(Oid relid); -- 2.11.0
From 669c0621d7970e04663117c43ba877ed53a804df Mon Sep 17 00:00:00 2001 From: amit <amitlangot...@gmail.com> Date: Wed, 9 Jan 2019 18:52:16 +0900 Subject: [PATCH v1 2/2] Do not track foreign key inheritance by dependencies Inheritance information maintained in pg_constraint is enough to prevent a child constraint to be dropped and for them to be dropped when the parent constraint is dropped. So, do not create dependencies between the parent foreign key constraint and its children. Also, fix ATAddForeignKeyConstraint to set inheritance information correctly. --- src/backend/catalog/pg_constraint.c | 33 +++++++-------------- src/backend/commands/indexcmds.c | 18 ++++++++++++ src/backend/commands/tablecmds.c | 57 ++++++++++++++++++++++++++++--------- 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index a379bec202..4f0d40473d 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -482,8 +482,6 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, Constraint *fkconstraint; bool attach_it; Oid constrOid; - ObjectAddress parentAddr, - childAddr; int nelem; ListCell *cell; int i; @@ -504,8 +502,6 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, continue; } - ObjectAddressSet(parentAddr, ConstraintRelationId, parentConstrOid); - datum = fastgetattr(tuple, Anum_pg_constraint_conkey, tupdesc, &isnull); if (isnull) @@ -708,9 +704,6 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, 1, false, true); subclone = lappend_oid(subclone, constrOid); - ObjectAddressSet(childAddr, ConstraintRelationId, constrOid); - recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL_AUTO); - fkconstraint = makeNode(Constraint); /* for now this is all we need */ fkconstraint->conname = pstrdup(NameStr(constrForm->conname)); @@ -1162,8 +1155,8 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, * ConstraintSetParentConstraint * Set a partition's constraint as child of its parent table's * - * This updates the constraint's pg_constraint row to show it as inherited, and - * add a dependency to the parent so that it cannot be removed on its own. + * This updates the constraint's pg_constraint row to change its + * inheritance properties. */ void ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) @@ -1172,8 +1165,6 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) Form_pg_constraint constrForm; HeapTuple tuple, newtup; - ObjectAddress depender; - ObjectAddress referenced; constrRel = heap_open(ConstraintRelationId, RowExclusiveLock); tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(childConstrId)); @@ -1185,25 +1176,23 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) { constrForm->conislocal = false; constrForm->coninhcount++; + /* + * An inherited foreign key constraint can never have more than one + * parent, because inheriting foreign keys is only allowed for + * partitioning where multiple inheritance is disallowed. + */ + Assert(constrForm->coninhcount == 1); constrForm->conparentid = parentConstrId; CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); - - ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId); - ObjectAddressSet(depender, ConstraintRelationId, childConstrId); - - recordDependencyOn(&depender, &referenced, DEPENDENCY_INTERNAL_AUTO); } else { constrForm->coninhcount--; - if (constrForm->coninhcount <= 0) - constrForm->conislocal = true; + /* See the above comment. */ + Assert(constrForm->coninhcount == 0); + constrForm->conislocal = true; constrForm->conparentid = InvalidOid; - - deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId, - ConstraintRelationId, - DEPENDENCY_INTERNAL_AUTO); CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); } diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 965b9f0d23..9e227796bc 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -974,9 +974,27 @@ DefineIndex(Oid relationId, /* Attach index to parent and we're done. */ IndexSetParentIndex(cldidx, indexRelationId); if (createdConstraintId != InvalidOid) + { + ObjectAddress depender; + ObjectAddress referenced; + ConstraintSetParentConstraint(cldConstrOid, createdConstraintId); + /* + * Need to set an explicit dependency in this + * case unlike other types of constraints where + * the child constraint gets dropped due to + * inheritance recursion. + */ + ObjectAddressSet(referenced, ConstraintRelationId, + createdConstraintId); + ObjectAddressSet(depender, ConstraintRelationId, + cldConstrOid); + recordDependencyOn(&depender, &referenced, + DEPENDENCY_INTERNAL_AUTO); + } + if (!IndexIsValid(cldidx->rd_index)) invalidate_parent = true; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ae83deaf51..1a5da20bf3 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7342,6 +7342,9 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, bool old_check_ok; ObjectAddress address; ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop); + bool conislocal = true; + int coninhcount = 0; + bool connoinherit = true; /* * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't @@ -7680,6 +7683,26 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* * Record the FK constraint in pg_constraint. */ + + /* + * Set the inheritance related properties correctly if it's being + * recursively added for a partition. + */ + if (OidIsValid(parentConstr)) + { + /* Foreign keys are inherited only for partitioning. */ + Assert(rel->rd_rel->relispartition); + conislocal = false; + /* Partitions can have only one parent. */ + coninhcount = 1; + /* Make sure that the constraint can be further inherited. */ + connoinherit = false; + } + + /* If partitioned table, constraint must be marked as inheritable. */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + connoinherit = false; + constrOid = CreateConstraintEntry(fkconstraint->conname, RelationGetNamespace(rel), CONSTRAINT_FOREIGN, @@ -7706,9 +7729,9 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, NULL, /* no check constraint */ NULL, NULL, - true, /* islocal */ - 0, /* inhcount */ - true, /* isnoinherit */ + conislocal, + coninhcount, + connoinherit, false); /* is_internal */ ObjectAddressSet(address, ConstraintRelationId, constrOid); @@ -7757,20 +7780,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Oid partitionId = partdesc->oids[i]; Relation partition = heap_open(partitionId, lockmode); AlteredTableInfo *childtab; - ObjectAddress childAddr; CheckTableNotInUse(partition, "ALTER TABLE"); /* Find or create work queue entry for this table */ childtab = ATGetQueueEntry(wqueue, partition); - childAddr = - ATAddForeignKeyConstraint(wqueue, childtab, partition, - fkconstraint, constrOid, - recurse, true, lockmode); - - /* Record this constraint as dependent on the parent one */ - recordDependencyOn(&childAddr, &address, DEPENDENCY_INTERNAL_AUTO); + ATAddForeignKeyConstraint(wqueue, childtab, partition, + fkconstraint, constrOid, + recurse, true, lockmode); heap_close(partition, NoLock); } @@ -9024,9 +9042,13 @@ ATExecDropConstraint(Relation rel, const char *constrName, con = (Form_pg_constraint) GETSTRUCT(copy_tuple); - /* Right now only CHECK constraints can be inherited */ - if (con->contype != CONSTRAINT_CHECK) - elog(ERROR, "inherited constraint is not a CHECK constraint"); + /* + * Right now only CHECK constraints and foreign key constraints can + * be inherited. + */ + if (con->contype != CONSTRAINT_CHECK && + con->contype != CONSTRAINT_FOREIGN) + elog(ERROR, "inherited constraint is not a CHECK constraint or a foreign key constraint"); if (con->coninhcount <= 0) /* shouldn't happen */ elog(ERROR, "relation %u has non-inherited constraint \"%s\"", @@ -14826,6 +14848,13 @@ ATExecDetachPartition(Relation rel, RangeVar *name) elog(ERROR, "cache lookup failed for constraint %u", fk->conoid); conform = (Form_pg_constraint) GETSTRUCT(contup); + /* No need to touch the partition's own foreign keys. */ + if (conform->conislocal) + { + ReleaseSysCache(contup); + continue; + } + ConstraintSetParentConstraint(fk->conoid, InvalidOid); /* -- 2.11.0
From f757e49a93aa5a43b673b973f900bdbf91bef8e4 Mon Sep 17 00:00:00 2001 From: amit <amitlangot...@gmail.com> Date: Wed, 9 Jan 2019 10:00:11 +0900 Subject: [PATCH v1 1/2] Ensure PK-side action triggers for partitions after being detached Detaching a partition from the parent whose foreign key(s) would've been duplicated in the partition makes the foreign key(s) stop working corretly. That's because PK-side action trigger for the foreign key would refer to the parent as the referencing relation and after the partition is detached it's data is no longer accessible via parent. So while the check triggers that remain even after being detached takes care of the one side of enforcing the foreign key of the detached partition, lack of corresponding PK-side action trigger to check detached partition's data means the other side doesn't work. To fix, add the action triggers on the PK relation that points to the detached partition when detaching. --- src/backend/catalog/pg_constraint.c | 3 +- src/backend/commands/tablecmds.c | 57 +++++++++++++++++++++++++++++++++++-- src/include/commands/tablecmds.h | 3 +- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 3c960c9423..c88e923327 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -714,7 +714,8 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, fkconstraint->initdeferred = constrForm->condeferred; createForeignKeyTriggers(partRel, constrForm->confrelid, fkconstraint, - constrOid, constrForm->conindid, false); + constrOid, constrForm->conindid, false, + true); if (cloned) { diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c75c5808ba..a3eb39ffdd 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7623,7 +7623,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * though the constraints also exist below. */ createForeignKeyTriggers(rel, RelationGetRelid(pkrel), fkconstraint, - constrOid, indexOid, !recursing); + constrOid, indexOid, !recursing, true); /* * Tell Phase 3 to check that the constraint is satisfied by existing @@ -8728,7 +8728,8 @@ createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid, */ void createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, - Oid constraintOid, Oid indexOid, bool create_action) + Oid constraintOid, Oid indexOid, bool create_action, + bool create_check) { /* * For the referenced side, create action triggers, if requested. (If the @@ -8744,7 +8745,7 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, * For the referencing side, create the check triggers. We only need * these on the partitions. */ - if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && create_check) createForeignKeyCheckTriggers(RelationGetRelid(rel), refRelOid, fkconstraint, constraintOid, indexOid); @@ -14675,13 +14676,63 @@ ATExecDetachPartition(Relation rel, RangeVar *name) { ForeignKeyCacheInfo *fk = lfirst(cell); HeapTuple contup; + Form_pg_constraint conform; + Constraint *fkconstraint; + Relation trigrel; + HeapTuple trigtup; + HeapScanDesc scan; + ScanKeyData key[3]; contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid)); if (!contup) elog(ERROR, "cache lookup failed for constraint %u", fk->conoid); + conform = (Form_pg_constraint) GETSTRUCT(contup); ConstraintSetParentConstraint(fk->conoid, InvalidOid); + /* + * We'll need to make the triggers on primary key relation to point + * to this relation as the FK relation. Currently, it points to + * the parent from which this relation is being detached. + */ + fkconstraint = makeNode(Constraint); + fkconstraint->conname = pstrdup(NameStr(conform->conname)); + fkconstraint->fk_upd_action = conform->confupdtype; + fkconstraint->fk_del_action = conform->confdeltype; + fkconstraint->deferrable = conform->condeferrable; + fkconstraint->initdeferred = conform->condeferred; + + /* First check if such a trigger doesn't already exist. */ + trigrel = heap_open(TriggerRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_trigger_tgrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(conform->confrelid)); + ScanKeyInit(&key[1], + Anum_pg_trigger_tgconstrrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(partRel))); + ScanKeyInit(&key[2], + Anum_pg_trigger_tgconstraint, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(fk->conoid)); + scan = heap_beginscan_catalog(trigrel, 3, key); + trigtup = heap_getnext(scan, ForwardScanDirection); + if (trigtup == NULL) + { + /* + * Nope, we didn't find such a trigger, so create one. + * + * As we already got the check triggers, no need to recreate them, + * so pass false for create_check. + */ + createForeignKeyTriggers(partRel, conform->confrelid, fkconstraint, + fk->conoid, conform->conindid, + true, false); + } + heap_endscan(scan); + heap_close(trigrel, AccessShareLock); + ReleaseSysCache(contup); } list_free_deep(fks); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index ec3bb90b01..6bebc68425 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -78,7 +78,8 @@ extern void check_of_type(HeapTuple typetuple); extern void createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, Oid constraintOid, - Oid indexOid, bool create_action); + Oid indexOid, bool create_action, + bool create_check); extern void register_on_commit_action(Oid relid, OnCommitAction action); extern void remove_on_commit_action(Oid relid); -- 2.11.0
From 92da65c2359db0bb6947a283a7996273d22a014a Mon Sep 17 00:00:00 2001 From: amit <amitlangot...@gmail.com> Date: Wed, 9 Jan 2019 14:01:47 +0900 Subject: [PATCH v1 2/2] Do not track foreign key inheritance by dependencies Inheritance information maintained in pg_constraint is enough to prevent a child constraint to be dropped and for them to be dropped when the parent constraint is dropped. So, do not create dependencies between the parent foreign key constraint and its children. Also, fix ATAddForeignKeyConstraint to set inheritance information correctly. --- src/backend/catalog/pg_constraint.c | 33 +++++++-------------- src/backend/commands/indexcmds.c | 18 ++++++++++++ src/backend/commands/tablecmds.c | 57 ++++++++++++++++++++++++++++--------- 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index c88e923327..60e4ff5594 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -478,8 +478,6 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, Constraint *fkconstraint; bool attach_it; Oid constrOid; - ObjectAddress parentAddr, - childAddr; int nelem; ListCell *cell; int i; @@ -500,8 +498,6 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, continue; } - ObjectAddressSet(parentAddr, ConstraintRelationId, parentConstrOid); - datum = fastgetattr(tuple, Anum_pg_constraint_conkey, tupdesc, &isnull); if (isnull) @@ -702,9 +698,6 @@ clone_fk_constraints(Relation pg_constraint, Relation parentRel, 1, false, true); subclone = lappend_oid(subclone, constrOid); - ObjectAddressSet(childAddr, ConstraintRelationId, constrOid); - recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL_AUTO); - fkconstraint = makeNode(Constraint); /* for now this is all we need */ fkconstraint->conname = pstrdup(NameStr(constrForm->conname)); @@ -1156,8 +1149,8 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId, * ConstraintSetParentConstraint * Set a partition's constraint as child of its parent table's * - * This updates the constraint's pg_constraint row to show it as inherited, and - * add a dependency to the parent so that it cannot be removed on its own. + * This updates the constraint's pg_constraint row to change its + * inheritance properties. */ void ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) @@ -1166,8 +1159,6 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) Form_pg_constraint constrForm; HeapTuple tuple, newtup; - ObjectAddress depender; - ObjectAddress referenced; constrRel = heap_open(ConstraintRelationId, RowExclusiveLock); tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(childConstrId)); @@ -1179,25 +1170,23 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId) { constrForm->conislocal = false; constrForm->coninhcount++; + /* + * An inherited foreign key constraint can never have more than one + * parent, because inheriting foreign keys is only allowed for + * partitioning where multiple inheritance is disallowed. + */ + Assert(constrForm->coninhcount == 1); constrForm->conparentid = parentConstrId; CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); - - ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId); - ObjectAddressSet(depender, ConstraintRelationId, childConstrId); - - recordDependencyOn(&depender, &referenced, DEPENDENCY_INTERNAL_AUTO); } else { constrForm->coninhcount--; - if (constrForm->coninhcount <= 0) - constrForm->conislocal = true; + /* See the above comment. */ + Assert(constrForm->coninhcount == 0); + constrForm->conislocal = true; constrForm->conparentid = InvalidOid; - - deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId, - ConstraintRelationId, - DEPENDENCY_INTERNAL_AUTO); CatalogTupleUpdate(constrRel, &tuple->t_self, newtup); } diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index d263903622..d8430f6fe5 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -972,9 +972,27 @@ DefineIndex(Oid relationId, /* Attach index to parent and we're done. */ IndexSetParentIndex(cldidx, indexRelationId); if (createdConstraintId != InvalidOid) + { + ObjectAddress depender; + ObjectAddress referenced; + ConstraintSetParentConstraint(cldConstrOid, createdConstraintId); + /* + * Need to set an explicit dependency in this + * case unlike other types of constraints where + * the child constraint gets dropped due to + * inheritance recursion. + */ + ObjectAddressSet(referenced, ConstraintRelationId, + createdConstraintId); + ObjectAddressSet(depender, ConstraintRelationId, + cldConstrOid); + recordDependencyOn(&depender, &referenced, + DEPENDENCY_INTERNAL_AUTO); + } + if (!cldidx->rd_index->indisvalid) invalidate_parent = true; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index a3eb39ffdd..8e2fb9b189 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7248,6 +7248,9 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, bool old_check_ok; ObjectAddress address; ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop); + bool conislocal = true; + int coninhcount = 0; + bool connoinherit = true; /* * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't @@ -7586,6 +7589,26 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* * Record the FK constraint in pg_constraint. */ + + /* + * Set the inheritance related properties correctly if it's being + * recursively added for a partition. + */ + if (OidIsValid(parentConstr)) + { + /* Foreign keys are inherited only for partitioning. */ + Assert(rel->rd_rel->relispartition); + conislocal = false; + /* Partitions can have only one parent. */ + coninhcount = 1; + /* Make sure that the constraint can be further inherited. */ + connoinherit = false; + } + + /* If partitioned table, constraint must be marked as inheritable. */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + connoinherit = false; + constrOid = CreateConstraintEntry(fkconstraint->conname, RelationGetNamespace(rel), CONSTRAINT_FOREIGN, @@ -7611,9 +7634,9 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, NULL, /* no exclusion constraint */ NULL, /* no check constraint */ NULL, - true, /* islocal */ - 0, /* inhcount */ - true, /* isnoinherit */ + conislocal, + coninhcount, + connoinherit, false); /* is_internal */ ObjectAddressSet(address, ConstraintRelationId, constrOid); @@ -7662,20 +7685,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, Oid partitionId = partdesc->oids[i]; Relation partition = heap_open(partitionId, lockmode); AlteredTableInfo *childtab; - ObjectAddress childAddr; CheckTableNotInUse(partition, "ALTER TABLE"); /* Find or create work queue entry for this table */ childtab = ATGetQueueEntry(wqueue, partition); - childAddr = - ATAddForeignKeyConstraint(wqueue, childtab, partition, - fkconstraint, constrOid, - recurse, true, lockmode); - - /* Record this constraint as dependent on the parent one */ - recordDependencyOn(&childAddr, &address, DEPENDENCY_INTERNAL_AUTO); + ATAddForeignKeyConstraint(wqueue, childtab, partition, + fkconstraint, constrOid, + recurse, true, lockmode); heap_close(partition, NoLock); } @@ -8925,9 +8943,13 @@ ATExecDropConstraint(Relation rel, const char *constrName, con = (Form_pg_constraint) GETSTRUCT(copy_tuple); - /* Right now only CHECK constraints can be inherited */ - if (con->contype != CONSTRAINT_CHECK) - elog(ERROR, "inherited constraint is not a CHECK constraint"); + /* + * Right now only CHECK constraints and foreign key constraints can + * be inherited. + */ + if (con->contype != CONSTRAINT_CHECK && + con->contype != CONSTRAINT_FOREIGN) + elog(ERROR, "inherited constraint is not a CHECK constraint or a foreign key constraint"); if (con->coninhcount <= 0) /* shouldn't happen */ elog(ERROR, "relation %u has non-inherited constraint \"%s\"", @@ -14688,6 +14710,13 @@ ATExecDetachPartition(Relation rel, RangeVar *name) elog(ERROR, "cache lookup failed for constraint %u", fk->conoid); conform = (Form_pg_constraint) GETSTRUCT(contup); + /* No need to touch the partition's own foreign keys. */ + if (conform->conislocal) + { + ReleaseSysCache(contup); + continue; + } + ConstraintSetParentConstraint(fk->conoid, InvalidOid); /* -- 2.11.0