mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-01 12:27:21 +02:00
Correct attach/detach logic for FKs in partitions
There was no code to handle foreign key constraints on partitioned tables in the case of ALTER TABLE DETACH; and if you happened to ATTACH a partition that already had an equivalent constraint, that one was ignored and a new constraint was created. Adding this to the fact that foreign key cloning reuses the constraint name on the partition instead of generating a new name (as it probably should, to cater to SQL standard rules about constraint naming within schemas), the result was a pretty poor user experience -- the most visible failure was that just detaching a partition and re-attaching it failed with an error such as ERROR: duplicate key value violates unique constraint "pg_constraint_conrelid_contypid_conname_index" DETAIL: Key (conrelid, contypid, conname)=(26702, 0, test_result_asset_id_fkey) already exists. because it would try to create an identically-named constraint in the partition. To make matters worse, if you tried to drop the constraint in the now-independent partition, that would fail because the constraint was still seen as dependent on the constraint in its former parent partitioned table: ERROR: cannot drop inherited constraint "test_result_asset_id_fkey" of relation "test_result_cbsystem_0001_0050_monthly_2018_09" This fix attacks the problem from two angles: first, when the partition is detached, the constraint is also marked as independent, so the drop now works. Second, when the partition is re-attached, we scan existing constraints searching for one matching the FK in the parent, and if one exists, we link that one to the parent constraint. So we don't end up with a duplicate -- and better yet, we don't need to scan the referenced table to verify that the constraint holds. To implement this I made a small change to previously planner-only struct ForeignKeyCacheInfo to contain the constraint OID; also relcache now maintains the list of FKs for partitioned tables too. Backpatch to 11. Reported-by: Michael Vitale (bug #15425) Discussion: https://postgr.es/m/15425-2dbc9d2aa999f816@postgresql.org
This commit is contained in:
parent
88670a4366
commit
355684ee3f
@ -19,6 +19,7 @@
|
|||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
#include "access/sysattr.h"
|
#include "access/sysattr.h"
|
||||||
#include "access/tupconvert.h"
|
#include "access/tupconvert.h"
|
||||||
|
#include "access/xact.h"
|
||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
#include "catalog/indexing.h"
|
#include "catalog/indexing.h"
|
||||||
#include "catalog/objectaccess.h"
|
#include "catalog/objectaccess.h"
|
||||||
@ -37,6 +38,10 @@
|
|||||||
#include "utils/tqual.h"
|
#include "utils/tqual.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void clone_fk_constraints(Relation pg_constraint, Relation parentRel,
|
||||||
|
Relation partRel, List *clone, List **cloned);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CreateConstraintEntry
|
* CreateConstraintEntry
|
||||||
* Create a constraint table entry.
|
* Create a constraint table entry.
|
||||||
@ -400,34 +405,74 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
Relation rel;
|
Relation rel;
|
||||||
ScanKeyData key;
|
ScanKeyData key;
|
||||||
SysScanDesc scan;
|
SysScanDesc scan;
|
||||||
TupleDesc tupdesc;
|
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
AttrNumber *attmap;
|
List *clone = NIL;
|
||||||
|
|
||||||
parentRel = heap_open(parentId, NoLock); /* already got lock */
|
parentRel = heap_open(parentId, NoLock); /* already got lock */
|
||||||
/* see ATAddForeignKeyConstraint about lock level */
|
/* see ATAddForeignKeyConstraint about lock level */
|
||||||
rel = heap_open(relationId, AccessExclusiveLock);
|
rel = heap_open(relationId, AccessExclusiveLock);
|
||||||
|
|
||||||
pg_constraint = heap_open(ConstraintRelationId, RowShareLock);
|
pg_constraint = heap_open(ConstraintRelationId, RowShareLock);
|
||||||
|
|
||||||
|
/* Obtain the list of constraints to clone or attach */
|
||||||
|
ScanKeyInit(&key,
|
||||||
|
Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
|
||||||
|
F_OIDEQ, ObjectIdGetDatum(parentId));
|
||||||
|
scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true,
|
||||||
|
NULL, 1, &key);
|
||||||
|
while ((tuple = systable_getnext(scan)) != NULL)
|
||||||
|
clone = lappend_oid(clone, HeapTupleGetOid(tuple));
|
||||||
|
systable_endscan(scan);
|
||||||
|
|
||||||
|
/* Do the actual work, recursing to partitions as needed */
|
||||||
|
clone_fk_constraints(pg_constraint, parentRel, rel, clone, cloned);
|
||||||
|
|
||||||
|
/* We're done. Clean up */
|
||||||
|
heap_close(parentRel, NoLock);
|
||||||
|
heap_close(rel, NoLock); /* keep lock till commit */
|
||||||
|
heap_close(pg_constraint, RowShareLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* clone_fk_constraints
|
||||||
|
* Recursive subroutine for CloneForeignKeyConstraints
|
||||||
|
*
|
||||||
|
* Clone the given list of FK constraints when a partition is attached.
|
||||||
|
*
|
||||||
|
* When cloning foreign keys to a partition, it may happen that equivalent
|
||||||
|
* constraints already exist in the partition for some of them. We can skip
|
||||||
|
* creating a clone in that case, and instead just attach the existing
|
||||||
|
* constraint to the one in the parent.
|
||||||
|
*
|
||||||
|
* This function recurses to partitions, if the new partition is partitioned;
|
||||||
|
* of course, only do this for FKs that were actually cloned.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
clone_fk_constraints(Relation pg_constraint, Relation parentRel,
|
||||||
|
Relation partRel, List *clone, List **cloned)
|
||||||
|
{
|
||||||
|
TupleDesc tupdesc;
|
||||||
|
AttrNumber *attmap;
|
||||||
|
List *partFKs;
|
||||||
|
List *subclone = NIL;
|
||||||
|
ListCell *cell;
|
||||||
|
|
||||||
tupdesc = RelationGetDescr(pg_constraint);
|
tupdesc = RelationGetDescr(pg_constraint);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The constraint key may differ, if the columns in the partition are
|
* The constraint key may differ, if the columns in the partition are
|
||||||
* different. This map is used to convert them.
|
* different. This map is used to convert them.
|
||||||
*/
|
*/
|
||||||
attmap = convert_tuples_by_name_map(RelationGetDescr(rel),
|
attmap = convert_tuples_by_name_map(RelationGetDescr(partRel),
|
||||||
RelationGetDescr(parentRel),
|
RelationGetDescr(parentRel),
|
||||||
gettext_noop("could not convert row type"));
|
gettext_noop("could not convert row type"));
|
||||||
|
|
||||||
ScanKeyInit(&key,
|
partFKs = copyObject(RelationGetFKeyList(partRel));
|
||||||
Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
|
|
||||||
F_OIDEQ, ObjectIdGetDatum(parentId));
|
|
||||||
scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId, true,
|
|
||||||
NULL, 1, &key);
|
|
||||||
|
|
||||||
while ((tuple = systable_getnext(scan)) != NULL)
|
foreach(cell, clone)
|
||||||
{
|
{
|
||||||
Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
|
Oid parentConstrOid = lfirst_oid(cell);
|
||||||
|
Form_pg_constraint constrForm;
|
||||||
|
HeapTuple tuple;
|
||||||
AttrNumber conkey[INDEX_MAX_KEYS];
|
AttrNumber conkey[INDEX_MAX_KEYS];
|
||||||
AttrNumber mapped_conkey[INDEX_MAX_KEYS];
|
AttrNumber mapped_conkey[INDEX_MAX_KEYS];
|
||||||
AttrNumber confkey[INDEX_MAX_KEYS];
|
AttrNumber confkey[INDEX_MAX_KEYS];
|
||||||
@ -435,22 +480,31 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
Oid conppeqop[INDEX_MAX_KEYS];
|
Oid conppeqop[INDEX_MAX_KEYS];
|
||||||
Oid conffeqop[INDEX_MAX_KEYS];
|
Oid conffeqop[INDEX_MAX_KEYS];
|
||||||
Constraint *fkconstraint;
|
Constraint *fkconstraint;
|
||||||
ClonedConstraint *newc;
|
bool attach_it;
|
||||||
Oid constrOid;
|
Oid constrOid;
|
||||||
ObjectAddress parentAddr,
|
ObjectAddress parentAddr,
|
||||||
childAddr;
|
childAddr;
|
||||||
int nelem;
|
int nelem;
|
||||||
|
ListCell *cell;
|
||||||
int i;
|
int i;
|
||||||
ArrayType *arr;
|
ArrayType *arr;
|
||||||
Datum datum;
|
Datum datum;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
|
|
||||||
|
tuple = SearchSysCache1(CONSTROID, parentConstrOid);
|
||||||
|
if (!tuple)
|
||||||
|
elog(ERROR, "cache lookup failed for constraint %u",
|
||||||
|
parentConstrOid);
|
||||||
|
constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
|
||||||
|
|
||||||
/* only foreign keys */
|
/* only foreign keys */
|
||||||
if (constrForm->contype != CONSTRAINT_FOREIGN)
|
if (constrForm->contype != CONSTRAINT_FOREIGN)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ObjectAddressSet(parentAddr, ConstraintRelationId,
|
ObjectAddressSet(parentAddr, ConstraintRelationId, parentConstrOid);
|
||||||
HeapTupleGetOid(tuple));
|
|
||||||
|
|
||||||
datum = fastgetattr(tuple, Anum_pg_constraint_conkey,
|
datum = fastgetattr(tuple, Anum_pg_constraint_conkey,
|
||||||
tupdesc, &isnull);
|
tupdesc, &isnull);
|
||||||
@ -539,6 +593,90 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
elog(ERROR, "conffeqop is not a 1-D OID array");
|
elog(ERROR, "conffeqop is not a 1-D OID array");
|
||||||
memcpy(conffeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid));
|
memcpy(conffeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Before creating a new constraint, see whether any existing FKs are
|
||||||
|
* fit for the purpose. If one is, attach the parent constraint to it,
|
||||||
|
* and don't clone anything. This way we avoid the expensive
|
||||||
|
* verification step and don't end up with a duplicate FK. This also
|
||||||
|
* means we don't consider this constraint when recursing to
|
||||||
|
* partitions.
|
||||||
|
*/
|
||||||
|
attach_it = false;
|
||||||
|
foreach(cell, partFKs)
|
||||||
|
{
|
||||||
|
ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, cell);
|
||||||
|
Form_pg_constraint partConstr;
|
||||||
|
HeapTuple partcontup;
|
||||||
|
|
||||||
|
attach_it = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do some quick & easy initial checks. If any of these fail, we
|
||||||
|
* cannot use this constraint, but keep looking.
|
||||||
|
*/
|
||||||
|
if (fk->confrelid != constrForm->confrelid || fk->nkeys != nelem)
|
||||||
|
{
|
||||||
|
attach_it = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (i = 0; i < nelem; i++)
|
||||||
|
{
|
||||||
|
if (fk->conkey[i] != mapped_conkey[i] ||
|
||||||
|
fk->confkey[i] != confkey[i] ||
|
||||||
|
fk->conpfeqop[i] != conpfeqop[i])
|
||||||
|
{
|
||||||
|
attach_it = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!attach_it)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Looks good so far; do some more extensive checks. Presumably
|
||||||
|
* the check for 'convalidated' could be dropped, since we don't
|
||||||
|
* really care about that, but let's be careful for now.
|
||||||
|
*/
|
||||||
|
partcontup = SearchSysCache1(CONSTROID,
|
||||||
|
ObjectIdGetDatum(fk->conoid));
|
||||||
|
if (!partcontup)
|
||||||
|
elog(ERROR, "cache lookup failed for constraint %u",
|
||||||
|
fk->conoid);
|
||||||
|
partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
|
||||||
|
if (OidIsValid(partConstr->conparentid) ||
|
||||||
|
!partConstr->convalidated ||
|
||||||
|
partConstr->condeferrable != constrForm->condeferrable ||
|
||||||
|
partConstr->condeferred != constrForm->condeferred ||
|
||||||
|
partConstr->confupdtype != constrForm->confupdtype ||
|
||||||
|
partConstr->confdeltype != constrForm->confdeltype ||
|
||||||
|
partConstr->confmatchtype != constrForm->confmatchtype)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(partcontup);
|
||||||
|
attach_it = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseSysCache(partcontup);
|
||||||
|
|
||||||
|
/* looks good! Attach this constraint */
|
||||||
|
ConstraintSetParentConstraint(fk->conoid,
|
||||||
|
HeapTupleGetOid(tuple));
|
||||||
|
CommandCounterIncrement();
|
||||||
|
attach_it = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we attached to an existing constraint, there is no need to
|
||||||
|
* create a new one. In fact, there's no need to recurse for this
|
||||||
|
* constraint to partitions, either.
|
||||||
|
*/
|
||||||
|
if (attach_it)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
constrOid =
|
constrOid =
|
||||||
CreateConstraintEntry(NameStr(constrForm->conname),
|
CreateConstraintEntry(NameStr(constrForm->conname),
|
||||||
constrForm->connamespace,
|
constrForm->connamespace,
|
||||||
@ -547,7 +685,7 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
constrForm->condeferred,
|
constrForm->condeferred,
|
||||||
constrForm->convalidated,
|
constrForm->convalidated,
|
||||||
HeapTupleGetOid(tuple),
|
HeapTupleGetOid(tuple),
|
||||||
relationId,
|
RelationGetRelid(partRel),
|
||||||
mapped_conkey,
|
mapped_conkey,
|
||||||
nelem,
|
nelem,
|
||||||
nelem,
|
nelem,
|
||||||
@ -568,6 +706,7 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
NULL,
|
NULL,
|
||||||
false,
|
false,
|
||||||
1, false, true);
|
1, false, true);
|
||||||
|
subclone = lappend_oid(subclone, constrOid);
|
||||||
|
|
||||||
ObjectAddressSet(childAddr, ConstraintRelationId, constrOid);
|
ObjectAddressSet(childAddr, ConstraintRelationId, constrOid);
|
||||||
recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL_AUTO);
|
recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL_AUTO);
|
||||||
@ -580,17 +719,19 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
fkconstraint->deferrable = constrForm->condeferrable;
|
fkconstraint->deferrable = constrForm->condeferrable;
|
||||||
fkconstraint->initdeferred = constrForm->condeferred;
|
fkconstraint->initdeferred = constrForm->condeferred;
|
||||||
|
|
||||||
createForeignKeyTriggers(rel, constrForm->confrelid, fkconstraint,
|
createForeignKeyTriggers(partRel, constrForm->confrelid, fkconstraint,
|
||||||
constrOid, constrForm->conindid, false);
|
constrOid, constrForm->conindid, false);
|
||||||
|
|
||||||
if (cloned)
|
if (cloned)
|
||||||
{
|
{
|
||||||
|
ClonedConstraint *newc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Feed back caller about the constraints we created, so that they
|
* Feed back caller about the constraints we created, so that they
|
||||||
* can set up constraint verification.
|
* can set up constraint verification.
|
||||||
*/
|
*/
|
||||||
newc = palloc(sizeof(ClonedConstraint));
|
newc = palloc(sizeof(ClonedConstraint));
|
||||||
newc->relid = relationId;
|
newc->relid = RelationGetRelid(partRel);
|
||||||
newc->refrelid = constrForm->confrelid;
|
newc->refrelid = constrForm->confrelid;
|
||||||
newc->conindid = constrForm->conindid;
|
newc->conindid = constrForm->conindid;
|
||||||
newc->conid = constrOid;
|
newc->conid = constrOid;
|
||||||
@ -598,25 +739,36 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
|
|
||||||
*cloned = lappend(*cloned, newc);
|
*cloned = lappend(*cloned, newc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
}
|
}
|
||||||
systable_endscan(scan);
|
|
||||||
|
|
||||||
pfree(attmap);
|
pfree(attmap);
|
||||||
|
list_free_deep(partFKs);
|
||||||
|
|
||||||
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
/*
|
||||||
|
* If the partition is partitioned, recurse to handle any constraints that
|
||||||
|
* were cloned.
|
||||||
|
*/
|
||||||
|
if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
|
||||||
|
subclone != NIL)
|
||||||
{
|
{
|
||||||
PartitionDesc partdesc = RelationGetPartitionDesc(rel);
|
PartitionDesc partdesc = RelationGetPartitionDesc(partRel);
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < partdesc->nparts; i++)
|
for (i = 0; i < partdesc->nparts; i++)
|
||||||
CloneForeignKeyConstraints(RelationGetRelid(rel),
|
{
|
||||||
partdesc->oids[i],
|
Relation childRel;
|
||||||
cloned);
|
|
||||||
}
|
|
||||||
|
|
||||||
heap_close(rel, NoLock); /* keep lock till commit */
|
childRel = heap_open(partdesc->oids[i], AccessExclusiveLock);
|
||||||
heap_close(parentRel, NoLock);
|
clone_fk_constraints(pg_constraint,
|
||||||
heap_close(pg_constraint, RowShareLock);
|
partRel,
|
||||||
|
childRel,
|
||||||
|
subclone,
|
||||||
|
cloned);
|
||||||
|
heap_close(childRel, NoLock); /* keep lock till commit */
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1028,17 +1180,33 @@ ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId)
|
|||||||
elog(ERROR, "cache lookup failed for constraint %u", childConstrId);
|
elog(ERROR, "cache lookup failed for constraint %u", childConstrId);
|
||||||
newtup = heap_copytuple(tuple);
|
newtup = heap_copytuple(tuple);
|
||||||
constrForm = (Form_pg_constraint) GETSTRUCT(newtup);
|
constrForm = (Form_pg_constraint) GETSTRUCT(newtup);
|
||||||
|
if (OidIsValid(parentConstrId))
|
||||||
|
{
|
||||||
constrForm->conislocal = false;
|
constrForm->conislocal = false;
|
||||||
constrForm->coninhcount++;
|
constrForm->coninhcount++;
|
||||||
constrForm->conparentid = parentConstrId;
|
constrForm->conparentid = parentConstrId;
|
||||||
|
|
||||||
CatalogTupleUpdate(constrRel, &tuple->t_self, newtup);
|
CatalogTupleUpdate(constrRel, &tuple->t_self, newtup);
|
||||||
ReleaseSysCache(tuple);
|
|
||||||
|
|
||||||
ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId);
|
ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId);
|
||||||
ObjectAddressSet(depender, ConstraintRelationId, childConstrId);
|
ObjectAddressSet(depender, ConstraintRelationId, childConstrId);
|
||||||
|
|
||||||
recordDependencyOn(&depender, &referenced, DEPENDENCY_INTERNAL_AUTO);
|
recordDependencyOn(&depender, &referenced, DEPENDENCY_INTERNAL_AUTO);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
constrForm->coninhcount--;
|
||||||
|
if (constrForm->coninhcount <= 0)
|
||||||
|
constrForm->conislocal = true;
|
||||||
|
constrForm->conparentid = InvalidOid;
|
||||||
|
|
||||||
|
deleteDependencyRecordsForClass(ConstraintRelationId, childConstrId,
|
||||||
|
ConstraintRelationId,
|
||||||
|
DEPENDENCY_INTERNAL_AUTO);
|
||||||
|
CatalogTupleUpdate(constrRel, &tuple->t_self, newtup);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
heap_close(constrRel, RowExclusiveLock);
|
heap_close(constrRel, RowExclusiveLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14035,6 +14035,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
|
|||||||
|
|
||||||
attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
|
attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* XXX I think it'd be a good idea to grab locks on all tables referenced
|
||||||
|
* by FKs at this point also.
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Must be owner of both parent and source table -- parent was checked by
|
* Must be owner of both parent and source table -- parent was checked by
|
||||||
* ATSimplePermissions call in ATPrepCmd
|
* ATSimplePermissions call in ATPrepCmd
|
||||||
@ -14607,6 +14612,7 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
|
|||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
Oid defaultPartOid;
|
Oid defaultPartOid;
|
||||||
List *indexes;
|
List *indexes;
|
||||||
|
List *fks;
|
||||||
ListCell *cell;
|
ListCell *cell;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -14682,6 +14688,23 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
|
|||||||
}
|
}
|
||||||
heap_close(classRel, RowExclusiveLock);
|
heap_close(classRel, RowExclusiveLock);
|
||||||
|
|
||||||
|
/* Detach foreign keys */
|
||||||
|
fks = copyObject(RelationGetFKeyList(partRel));
|
||||||
|
foreach(cell, fks)
|
||||||
|
{
|
||||||
|
ForeignKeyCacheInfo *fk = lfirst(cell);
|
||||||
|
HeapTuple contup;
|
||||||
|
|
||||||
|
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
|
||||||
|
if (!contup)
|
||||||
|
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
|
||||||
|
|
||||||
|
ConstraintSetParentConstraint(fk->conoid, InvalidOid);
|
||||||
|
|
||||||
|
ReleaseSysCache(contup);
|
||||||
|
}
|
||||||
|
list_free_deep(fks);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Invalidate the parent's relcache so that the partition is no longer
|
* Invalidate the parent's relcache so that the partition is no longer
|
||||||
* included in its partition descriptor.
|
* included in its partition descriptor.
|
||||||
|
@ -4746,6 +4746,7 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
|
|||||||
{
|
{
|
||||||
ForeignKeyCacheInfo *newnode = makeNode(ForeignKeyCacheInfo);
|
ForeignKeyCacheInfo *newnode = makeNode(ForeignKeyCacheInfo);
|
||||||
|
|
||||||
|
COPY_SCALAR_FIELD(conoid);
|
||||||
COPY_SCALAR_FIELD(conrelid);
|
COPY_SCALAR_FIELD(conrelid);
|
||||||
COPY_SCALAR_FIELD(confrelid);
|
COPY_SCALAR_FIELD(confrelid);
|
||||||
COPY_SCALAR_FIELD(nkeys);
|
COPY_SCALAR_FIELD(nkeys);
|
||||||
|
@ -3634,6 +3634,7 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
|
|||||||
|
|
||||||
WRITE_NODE_TYPE("FOREIGNKEYCACHEINFO");
|
WRITE_NODE_TYPE("FOREIGNKEYCACHEINFO");
|
||||||
|
|
||||||
|
WRITE_OID_FIELD(conoid);
|
||||||
WRITE_OID_FIELD(conrelid);
|
WRITE_OID_FIELD(conrelid);
|
||||||
WRITE_OID_FIELD(confrelid);
|
WRITE_OID_FIELD(confrelid);
|
||||||
WRITE_INT_FIELD(nkeys);
|
WRITE_INT_FIELD(nkeys);
|
||||||
|
6
src/backend/utils/cache/relcache.c
vendored
6
src/backend/utils/cache/relcache.c
vendored
@ -4108,8 +4108,9 @@ RelationGetFKeyList(Relation relation)
|
|||||||
if (relation->rd_fkeyvalid)
|
if (relation->rd_fkeyvalid)
|
||||||
return relation->rd_fkeylist;
|
return relation->rd_fkeylist;
|
||||||
|
|
||||||
/* Fast path: if it doesn't have any triggers, it can't have FKs */
|
/* Fast path: non-partitioned tables without triggers can't have FKs */
|
||||||
if (!relation->rd_rel->relhastriggers)
|
if (!relation->rd_rel->relhastriggers &&
|
||||||
|
relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
||||||
return NIL;
|
return NIL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -4144,6 +4145,7 @@ RelationGetFKeyList(Relation relation)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
info = makeNode(ForeignKeyCacheInfo);
|
info = makeNode(ForeignKeyCacheInfo);
|
||||||
|
info->conoid = HeapTupleGetOid(htup);
|
||||||
info->conrelid = constraint->conrelid;
|
info->conrelid = constraint->conrelid;
|
||||||
info->confrelid = constraint->confrelid;
|
info->confrelid = constraint->confrelid;
|
||||||
|
|
||||||
|
@ -202,12 +202,13 @@ typedef struct RelationData
|
|||||||
* The per-FK-column arrays can be fixed-size because we allow at most
|
* The per-FK-column arrays can be fixed-size because we allow at most
|
||||||
* INDEX_MAX_KEYS columns in a foreign key constraint.
|
* INDEX_MAX_KEYS columns in a foreign key constraint.
|
||||||
*
|
*
|
||||||
* Currently, we only cache fields of interest to the planner, but the
|
* Currently, we mostly cache fields of interest to the planner, but the set
|
||||||
* set of fields could be expanded in future.
|
* of fields has already grown the constraint OID for other uses.
|
||||||
*/
|
*/
|
||||||
typedef struct ForeignKeyCacheInfo
|
typedef struct ForeignKeyCacheInfo
|
||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
|
Oid conoid; /* oid of the constraint itself */
|
||||||
Oid conrelid; /* relation constrained by the foreign key */
|
Oid conrelid; /* relation constrained by the foreign key */
|
||||||
Oid confrelid; /* relation referenced by the foreign key */
|
Oid confrelid; /* relation referenced by the foreign key */
|
||||||
int nkeys; /* number of columns in the foreign key */
|
int nkeys; /* number of columns in the foreign key */
|
||||||
|
@ -1648,6 +1648,125 @@ SELECT * FROM fk_partitioned_fk WHERE a = 142857;
|
|||||||
|
|
||||||
-- verify that DROP works
|
-- verify that DROP works
|
||||||
DROP TABLE fk_partitioned_fk_2;
|
DROP TABLE fk_partitioned_fk_2;
|
||||||
|
-- Test behavior of the constraint together with attaching and detaching
|
||||||
|
-- partitions.
|
||||||
|
CREATE TABLE fk_partitioned_fk_2 PARTITION OF fk_partitioned_fk FOR VALUES IN (1500,1502);
|
||||||
|
ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_2;
|
||||||
|
BEGIN;
|
||||||
|
DROP TABLE fk_partitioned_fk;
|
||||||
|
-- constraint should still be there
|
||||||
|
\d fk_partitioned_fk_2;
|
||||||
|
Table "public.fk_partitioned_fk_2"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
a | integer | | | 2501
|
||||||
|
b | integer | | | 142857
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
DROP TABLE fk_partitioned_fk_2;
|
||||||
|
CREATE TABLE fk_partitioned_fk_2 (b int, c text, a int,
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE);
|
||||||
|
ALTER TABLE fk_partitioned_fk_2 DROP COLUMN c;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
-- should have only one constraint
|
||||||
|
\d fk_partitioned_fk_2
|
||||||
|
Table "public.fk_partitioned_fk_2"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
b | integer | | |
|
||||||
|
a | integer | | |
|
||||||
|
Partition of: fk_partitioned_fk FOR VALUES IN (1500, 1502)
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_partitioned_fk_2_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
|
||||||
|
DROP TABLE fk_partitioned_fk_2;
|
||||||
|
CREATE TABLE fk_partitioned_fk_4 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE) PARTITION BY RANGE (b, a);
|
||||||
|
CREATE TABLE fk_partitioned_fk_4_1 PARTITION OF fk_partitioned_fk_4 FOR VALUES FROM (1,1) TO (100,100);
|
||||||
|
CREATE TABLE fk_partitioned_fk_4_2 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE SET NULL);
|
||||||
|
ALTER TABLE fk_partitioned_fk_4 ATTACH PARTITION fk_partitioned_fk_4_2 FOR VALUES FROM (100,100) TO (1000,1000);
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FOR VALUES IN (3500,3502);
|
||||||
|
ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_4;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FOR VALUES IN (3500,3502);
|
||||||
|
-- should only have one constraint
|
||||||
|
\d fk_partitioned_fk_4
|
||||||
|
Table "public.fk_partitioned_fk_4"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
a | integer | | |
|
||||||
|
b | integer | | |
|
||||||
|
Partition of: fk_partitioned_fk FOR VALUES IN (3500, 3502)
|
||||||
|
Partition key: RANGE (b, a)
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_partitioned_fk_4_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
Number of partitions: 2 (Use \d+ to list them.)
|
||||||
|
|
||||||
|
\d fk_partitioned_fk_4_1
|
||||||
|
Table "public.fk_partitioned_fk_4_1"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
a | integer | | |
|
||||||
|
b | integer | | |
|
||||||
|
Partition of: fk_partitioned_fk_4 FOR VALUES FROM (1, 1) TO (100, 100)
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_partitioned_fk_4_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
|
||||||
|
-- this one has an FK with mismatched properties
|
||||||
|
\d fk_partitioned_fk_4_2
|
||||||
|
Table "public.fk_partitioned_fk_4_2"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
a | integer | | |
|
||||||
|
b | integer | | |
|
||||||
|
Partition of: fk_partitioned_fk_4 FOR VALUES FROM (100, 100) TO (1000, 1000)
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_partitioned_fk_4_2_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE SET NULL
|
||||||
|
"fk_partitioned_fk_4_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
|
||||||
|
CREATE TABLE fk_partitioned_fk_5 (a int, b int,
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE,
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE)
|
||||||
|
PARTITION BY RANGE (a);
|
||||||
|
CREATE TABLE fk_partitioned_fk_5_1 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk);
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FOR VALUES IN (4500);
|
||||||
|
ALTER TABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FOR VALUES FROM (0) TO (10);
|
||||||
|
ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_5;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FOR VALUES IN (4500);
|
||||||
|
-- this one has two constraints, similar but not quite the one in the parent,
|
||||||
|
-- so it gets a new one
|
||||||
|
\d fk_partitioned_fk_5
|
||||||
|
Table "public.fk_partitioned_fk_5"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
a | integer | | |
|
||||||
|
b | integer | | |
|
||||||
|
Partition of: fk_partitioned_fk FOR VALUES IN (4500)
|
||||||
|
Partition key: RANGE (a)
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_partitioned_fk_5_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE
|
||||||
|
"fk_partitioned_fk_5_a_fkey1" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
"fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
Number of partitions: 1 (Use \d+ to list them.)
|
||||||
|
|
||||||
|
-- verify that it works to reattaching a child with multiple candidate
|
||||||
|
-- constraints
|
||||||
|
ALTER TABLE fk_partitioned_fk_5 DETACH PARTITION fk_partitioned_fk_5_1;
|
||||||
|
ALTER TABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FOR VALUES FROM (0) TO (10);
|
||||||
|
\d fk_partitioned_fk_5_1
|
||||||
|
Table "public.fk_partitioned_fk_5_1"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
a | integer | | |
|
||||||
|
b | integer | | |
|
||||||
|
Partition of: fk_partitioned_fk_5 FOR VALUES FROM (0) TO (10)
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_partitioned_fk_5_1_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b)
|
||||||
|
"fk_partitioned_fk_5_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE
|
||||||
|
"fk_partitioned_fk_5_a_fkey1" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
"fk_partitioned_fk_a_fkey" FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
|
||||||
-- verify that attaching a table checks that the existing data satisfies the
|
-- verify that attaching a table checks that the existing data satisfies the
|
||||||
-- constraint
|
-- constraint
|
||||||
CREATE TABLE fk_partitioned_fk_2 (a int, b int) PARTITION BY RANGE (b);
|
CREATE TABLE fk_partitioned_fk_2 (a int, b int) PARTITION BY RANGE (b);
|
||||||
|
@ -1226,6 +1226,56 @@ SELECT * FROM fk_partitioned_fk WHERE a = 142857;
|
|||||||
-- verify that DROP works
|
-- verify that DROP works
|
||||||
DROP TABLE fk_partitioned_fk_2;
|
DROP TABLE fk_partitioned_fk_2;
|
||||||
|
|
||||||
|
-- Test behavior of the constraint together with attaching and detaching
|
||||||
|
-- partitions.
|
||||||
|
CREATE TABLE fk_partitioned_fk_2 PARTITION OF fk_partitioned_fk FOR VALUES IN (1500,1502);
|
||||||
|
ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_2;
|
||||||
|
BEGIN;
|
||||||
|
DROP TABLE fk_partitioned_fk;
|
||||||
|
-- constraint should still be there
|
||||||
|
\d fk_partitioned_fk_2;
|
||||||
|
ROLLBACK;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
DROP TABLE fk_partitioned_fk_2;
|
||||||
|
CREATE TABLE fk_partitioned_fk_2 (b int, c text, a int,
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk ON UPDATE CASCADE ON DELETE CASCADE);
|
||||||
|
ALTER TABLE fk_partitioned_fk_2 DROP COLUMN c;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES IN (1500,1502);
|
||||||
|
-- should have only one constraint
|
||||||
|
\d fk_partitioned_fk_2
|
||||||
|
DROP TABLE fk_partitioned_fk_2;
|
||||||
|
|
||||||
|
CREATE TABLE fk_partitioned_fk_4 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE) PARTITION BY RANGE (b, a);
|
||||||
|
CREATE TABLE fk_partitioned_fk_4_1 PARTITION OF fk_partitioned_fk_4 FOR VALUES FROM (1,1) TO (100,100);
|
||||||
|
CREATE TABLE fk_partitioned_fk_4_2 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE SET NULL);
|
||||||
|
ALTER TABLE fk_partitioned_fk_4 ATTACH PARTITION fk_partitioned_fk_4_2 FOR VALUES FROM (100,100) TO (1000,1000);
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FOR VALUES IN (3500,3502);
|
||||||
|
ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_4;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_4 FOR VALUES IN (3500,3502);
|
||||||
|
-- should only have one constraint
|
||||||
|
\d fk_partitioned_fk_4
|
||||||
|
\d fk_partitioned_fk_4_1
|
||||||
|
-- this one has an FK with mismatched properties
|
||||||
|
\d fk_partitioned_fk_4_2
|
||||||
|
|
||||||
|
CREATE TABLE fk_partitioned_fk_5 (a int, b int,
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE,
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk(a, b) MATCH FULL ON UPDATE CASCADE ON DELETE CASCADE)
|
||||||
|
PARTITION BY RANGE (a);
|
||||||
|
CREATE TABLE fk_partitioned_fk_5_1 (a int, b int, FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk);
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FOR VALUES IN (4500);
|
||||||
|
ALTER TABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FOR VALUES FROM (0) TO (10);
|
||||||
|
ALTER TABLE fk_partitioned_fk DETACH PARTITION fk_partitioned_fk_5;
|
||||||
|
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_5 FOR VALUES IN (4500);
|
||||||
|
-- this one has two constraints, similar but not quite the one in the parent,
|
||||||
|
-- so it gets a new one
|
||||||
|
\d fk_partitioned_fk_5
|
||||||
|
-- verify that it works to reattaching a child with multiple candidate
|
||||||
|
-- constraints
|
||||||
|
ALTER TABLE fk_partitioned_fk_5 DETACH PARTITION fk_partitioned_fk_5_1;
|
||||||
|
ALTER TABLE fk_partitioned_fk_5 ATTACH PARTITION fk_partitioned_fk_5_1 FOR VALUES FROM (0) TO (10);
|
||||||
|
\d fk_partitioned_fk_5_1
|
||||||
|
|
||||||
-- verify that attaching a table checks that the existing data satisfies the
|
-- verify that attaching a table checks that the existing data satisfies the
|
||||||
-- constraint
|
-- constraint
|
||||||
CREATE TABLE fk_partitioned_fk_2 (a int, b int) PARTITION BY RANGE (b);
|
CREATE TABLE fk_partitioned_fk_2 (a int, b int) PARTITION BY RANGE (b);
|
||||||
|
Loading…
Reference in New Issue
Block a user