Restructure foreign key handling code for ATTACH/DETACH
... to fix bugs when the referenced table is partitioned. The catalog representation we chose for foreign keys connecting partitioned tables (in commit f56f8f8da6af) is inconvenient, in the sense that a standalone table has a different way to represent the constraint when referencing a partitioned table, than when the same table becomes a partition (and vice versa). Because of this, we need to create additional catalog rows on detach (pg_constraint and pg_trigger), and remove them on attach. We were doing some of those things, but not all of them, leading to missing catalog rows in certain cases. The worst problem seems to be that we are missing action triggers after detaching a partition, which means that you could update/delete rows from the referenced partitioned table that still had referencing rows on that table, the server failing to throw the required errors. !!! Note that this means existing databases with FKs that reference partitioned tables might have rows that break relational integrity, on tables that were once partitions on the referencing side of the FK. Another possible problem is that trying to reattach a table that had been detached would fail indicating that internal triggers cannot be found, which from the user's point of view is nonsensical. In branches 15 and above, we fix this by creating a new helper function addFkConstraint() which is in charge of creating a standalone pg_constraint row, and repurposing addFkRecurseReferencing() and addFkRecurseReferenced() so that they're only the recursive routine for each side of the FK, and they call addFkConstraint() to create pg_constraint at each partitioning level and add the necessary triggers. These new routines can be used during partition creation, partition attach and detach, and foreign key creation. This reduces redundant code and simplifies the flow. In branches 14 and 13, we have a much simpler fix that consists on simply removing the constraint on detach. The reason is that those branches are missing commit f4566345cf40, which reworked the way this works in a way that we didn't consider back-patchable at the time. We opted to leave branch 12 alone, because it's different from branch 13 enough that the fix doesn't apply; and because it is going in EOL mode very soon, patching it now might be worse since there's no way to undo the damage if it goes wrong. Existing databases might need to be repaired. In the future we might want to rethink the catalog representation to avoid this problem, but for now the code seems to do what's required to make the constraints operate correctly. Co-authored-by: Jehan-Guillaume de Rorthais <jgdr@dalibo.com> Co-authored-by: Tender Wang <tndrwang@gmail.com> Co-authored-by: Alvaro Herrera <alvherre@alvh.no-ip.org> Reported-by: Guillaume Lelarge <guillaume@lelarge.info> Reported-by: Jehan-Guillaume de Rorthais <jgdr@dalibo.com> Reported-by: Thomas Baehler (SBB CFF FFS) <thomas.baehler2@sbb.ch> Discussion: https://postgr.es/m/20230420144344.40744130@karst Discussion: https://postgr.es/m/20230705233028.2f554f73@karst Discussion: https://postgr.es/m/GVAP278MB02787E7134FD691861635A8BC9032@GVAP278MB0278.CHEP278.PROD.OUTLOOK.COM Discussion: https://postgr.es/m/18541-628a61bc267cd2d3@postgresql.org
This commit is contained in:
parent
e1555645d7
commit
53af9491a0
@ -349,6 +349,14 @@ typedef struct ForeignTruncateInfo
|
|||||||
List *rels;
|
List *rels;
|
||||||
} ForeignTruncateInfo;
|
} ForeignTruncateInfo;
|
||||||
|
|
||||||
|
/* Partial or complete FK creation in addFkConstraint() */
|
||||||
|
typedef enum addFkConstraintSides
|
||||||
|
{
|
||||||
|
addFkReferencedSide,
|
||||||
|
addFkReferencingSide,
|
||||||
|
addFkBothSides,
|
||||||
|
} addFkConstraintSides;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Partition tables are expected to be dropped when the parent partitioned
|
* Partition tables are expected to be dropped when the parent partitioned
|
||||||
* table gets dropped. Hence for partitioning we use AUTO dependency.
|
* table gets dropped. Hence for partitioning we use AUTO dependency.
|
||||||
@ -508,17 +516,27 @@ static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *
|
|||||||
Relation rel, Constraint *fkconstraint,
|
Relation rel, Constraint *fkconstraint,
|
||||||
bool recurse, bool recursing,
|
bool recurse, bool recursing,
|
||||||
LOCKMODE lockmode);
|
LOCKMODE lockmode);
|
||||||
static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint,
|
|
||||||
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
|
|
||||||
int numfks, int16 *pkattnum, int16 *fkattnum,
|
|
||||||
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
|
||||||
int numfkdelsetcols, int16 *fkdelsetcols,
|
|
||||||
bool old_check_ok,
|
|
||||||
Oid parentDelTrigger, Oid parentUpdTrigger,
|
|
||||||
bool with_period);
|
|
||||||
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
|
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
|
||||||
int numfksetcols, const int16 *fksetcolsattnums,
|
int numfksetcols, const int16 *fksetcolsattnums,
|
||||||
List *fksetcols);
|
List *fksetcols);
|
||||||
|
static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
|
||||||
|
char *constraintname,
|
||||||
|
Constraint *fkconstraint, Relation rel,
|
||||||
|
Relation pkrel, Oid indexOid,
|
||||||
|
Oid parentConstr,
|
||||||
|
int numfks, int16 *pkattnum, int16 *fkattnum,
|
||||||
|
Oid *pfeqoperators, Oid *ppeqoperators,
|
||||||
|
Oid *ffeqoperators, int numfkdelsetcols,
|
||||||
|
int16 *fkdelsetcols, bool is_internal,
|
||||||
|
bool with_period);
|
||||||
|
static void addFkRecurseReferenced(Constraint *fkconstraint,
|
||||||
|
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
|
||||||
|
int numfks, int16 *pkattnum, int16 *fkattnum,
|
||||||
|
Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
|
||||||
|
int numfkdelsetcols, int16 *fkdelsetcols,
|
||||||
|
bool old_check_ok,
|
||||||
|
Oid parentDelTrigger, Oid parentUpdTrigger,
|
||||||
|
bool with_period);
|
||||||
static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
|
static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
|
||||||
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
|
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
|
||||||
int numfks, int16 *pkattnum, int16 *fkattnum,
|
int numfks, int16 *pkattnum, int16 *fkattnum,
|
||||||
@ -527,7 +545,6 @@ static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
|
|||||||
bool old_check_ok, LOCKMODE lockmode,
|
bool old_check_ok, LOCKMODE lockmode,
|
||||||
Oid parentInsTrigger, Oid parentUpdTrigger,
|
Oid parentInsTrigger, Oid parentUpdTrigger,
|
||||||
bool with_period);
|
bool with_period);
|
||||||
|
|
||||||
static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
|
static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
|
||||||
Relation partitionRel);
|
Relation partitionRel);
|
||||||
static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
|
static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
|
||||||
@ -10045,26 +10062,39 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
|||||||
FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid);
|
FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* First, create the constraint catalog entry itself. */
|
||||||
* Create all the constraint and trigger objects, recursing to partitions
|
address = addFkConstraint(addFkBothSides,
|
||||||
* as necessary. First handle the referenced side.
|
fkconstraint->conname, fkconstraint, rel, pkrel,
|
||||||
*/
|
indexOid,
|
||||||
address = addFkRecurseReferenced(wqueue, fkconstraint, rel, pkrel,
|
InvalidOid, /* no parent constraint */
|
||||||
indexOid,
|
numfks,
|
||||||
InvalidOid, /* no parent constraint */
|
pkattnum,
|
||||||
numfks,
|
fkattnum,
|
||||||
pkattnum,
|
pfeqoperators,
|
||||||
fkattnum,
|
ppeqoperators,
|
||||||
pfeqoperators,
|
ffeqoperators,
|
||||||
ppeqoperators,
|
numfkdelsetcols,
|
||||||
ffeqoperators,
|
fkdelsetcols,
|
||||||
numfkdelsetcols,
|
false,
|
||||||
fkdelsetcols,
|
with_period);
|
||||||
old_check_ok,
|
|
||||||
InvalidOid, InvalidOid,
|
|
||||||
with_period);
|
|
||||||
|
|
||||||
/* Now handle the referencing side. */
|
/* Next process the action triggers at the referenced side and recurse */
|
||||||
|
addFkRecurseReferenced(fkconstraint, rel, pkrel,
|
||||||
|
indexOid,
|
||||||
|
address.objectId,
|
||||||
|
numfks,
|
||||||
|
pkattnum,
|
||||||
|
fkattnum,
|
||||||
|
pfeqoperators,
|
||||||
|
ppeqoperators,
|
||||||
|
ffeqoperators,
|
||||||
|
numfkdelsetcols,
|
||||||
|
fkdelsetcols,
|
||||||
|
old_check_ok,
|
||||||
|
InvalidOid, InvalidOid,
|
||||||
|
with_period);
|
||||||
|
|
||||||
|
/* Lastly create the check triggers at the referencing side and recurse */
|
||||||
addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
|
addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
|
||||||
indexOid,
|
indexOid,
|
||||||
address.objectId,
|
address.objectId,
|
||||||
@ -10125,47 +10155,42 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* addFkRecurseReferenced
|
* addFkConstraint
|
||||||
* subroutine for ATAddForeignKeyConstraint; recurses on the referenced
|
* Install pg_constraint entries to implement a foreign key constraint.
|
||||||
* side of the constraint
|
* Caller must separately invoke addFkRecurseReferenced and
|
||||||
|
* addFkRecurseReferencing, as appropriate, to install pg_trigger entries
|
||||||
|
* and (for partitioned tables) recurse to partitions.
|
||||||
*
|
*
|
||||||
* Create pg_constraint rows for the referenced side of the constraint,
|
* fkside: the side of the FK (or both) to create. Caller should
|
||||||
* referencing the parent of the referencing side; also create action triggers
|
* call addFkRecurseReferenced if this is addFkReferencedSide,
|
||||||
* on leaf partitions. If the table is partitioned, recurse to handle each
|
* addFkRecurseReferencing if it's addFkReferencingSide, or both if it's
|
||||||
* partition.
|
* addFkBothSides.
|
||||||
*
|
* constraintname: the base name for the constraint being added,
|
||||||
* wqueue is the ALTER TABLE work queue; can be NULL when not running as part
|
* copied to fkconstraint->conname if the latter is not set
|
||||||
* of an ALTER TABLE sequence.
|
* fkconstraint: the constraint being added
|
||||||
* fkconstraint is the constraint being added.
|
* rel: the root referencing relation
|
||||||
* rel is the root referencing relation.
|
* pkrel: the referenced relation; might be a partition, if recursing
|
||||||
* pkrel is the referenced relation; might be a partition, if recursing.
|
* indexOid: the OID of the index (on pkrel) implementing this constraint
|
||||||
* indexOid is the OID of the index (on pkrel) implementing this constraint.
|
* parentConstr: the OID of a parent constraint; InvalidOid if this is a
|
||||||
* parentConstr is the OID of a parent constraint; InvalidOid if this is a
|
* top-level constraint
|
||||||
* top-level constraint.
|
* numfks: the number of columns in the foreign key
|
||||||
* numfks is the number of columns in the foreign key
|
* pkattnum: the attnum array of referenced attributes
|
||||||
* pkattnum is the attnum array of referenced attributes.
|
* fkattnum: the attnum array of referencing attributes
|
||||||
* fkattnum is the attnum array of referencing attributes.
|
* pf/pp/ffeqoperators: OID array of operators between columns
|
||||||
* numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DEFAULT
|
* numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
|
||||||
* (...) clause
|
* (...) clause
|
||||||
* fkdelsetcols is the attnum array of the columns in the ON DELETE SET
|
* fkdelsetcols: the attnum array of the columns in the ON DELETE SET
|
||||||
* NULL/DEFAULT clause
|
* NULL/DEFAULT clause
|
||||||
* pf/pp/ffeqoperators are OID array of operators between columns.
|
* with_period: true if this is a temporal FK
|
||||||
* old_check_ok signals that this constraint replaces an existing one that
|
|
||||||
* was already validated (thus this one doesn't need validation).
|
|
||||||
* parentDelTrigger and parentUpdTrigger, when being recursively called on
|
|
||||||
* a partition, are the OIDs of the parent action triggers for DELETE and
|
|
||||||
* UPDATE respectively.
|
|
||||||
*/
|
*/
|
||||||
static ObjectAddress
|
static ObjectAddress
|
||||||
addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
addFkConstraint(addFkConstraintSides fkside,
|
||||||
Relation pkrel, Oid indexOid, Oid parentConstr,
|
char *constraintname, Constraint *fkconstraint,
|
||||||
int numfks,
|
Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
|
||||||
int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
|
int numfks, int16 *pkattnum,
|
||||||
Oid *ppeqoperators, Oid *ffeqoperators,
|
int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators,
|
||||||
int numfkdelsetcols, int16 *fkdelsetcols,
|
Oid *ffeqoperators, int numfkdelsetcols, int16 *fkdelsetcols,
|
||||||
bool old_check_ok,
|
bool is_internal, bool with_period)
|
||||||
Oid parentDelTrigger, Oid parentUpdTrigger,
|
|
||||||
bool with_period)
|
|
||||||
{
|
{
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
Oid constrOid;
|
Oid constrOid;
|
||||||
@ -10173,8 +10198,6 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
bool conislocal;
|
bool conislocal;
|
||||||
int16 coninhcount;
|
int16 coninhcount;
|
||||||
bool connoinherit;
|
bool connoinherit;
|
||||||
Oid deleteTriggerOid,
|
|
||||||
updateTriggerOid;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify relkind for each referenced partition. At the top level, this
|
* Verify relkind for each referenced partition. At the top level, this
|
||||||
@ -10193,13 +10216,16 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
*/
|
*/
|
||||||
if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
|
if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
|
||||||
RelationGetRelid(rel),
|
RelationGetRelid(rel),
|
||||||
fkconstraint->conname))
|
constraintname))
|
||||||
conname = ChooseConstraintName(RelationGetRelationName(rel),
|
conname = ChooseConstraintName(RelationGetRelationName(rel),
|
||||||
ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
|
ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
|
||||||
"fkey",
|
"fkey",
|
||||||
RelationGetNamespace(rel), NIL);
|
RelationGetNamespace(rel), NIL);
|
||||||
else
|
else
|
||||||
conname = fkconstraint->conname;
|
conname = constraintname;
|
||||||
|
|
||||||
|
if (fkconstraint->conname == NULL)
|
||||||
|
fkconstraint->conname = pstrdup(conname);
|
||||||
|
|
||||||
if (OidIsValid(parentConstr))
|
if (OidIsValid(parentConstr))
|
||||||
{
|
{
|
||||||
@ -10252,33 +10278,106 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
coninhcount, /* inhcount */
|
coninhcount, /* inhcount */
|
||||||
connoinherit, /* conNoInherit */
|
connoinherit, /* conNoInherit */
|
||||||
with_period, /* conPeriod */
|
with_period, /* conPeriod */
|
||||||
false); /* is_internal */
|
is_internal); /* is_internal */
|
||||||
|
|
||||||
ObjectAddressSet(address, ConstraintRelationId, constrOid);
|
ObjectAddressSet(address, ConstraintRelationId, constrOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mark the child constraint as part of the parent constraint; it must not
|
* In partitioning cases, create the dependency entries for this
|
||||||
* be dropped on its own. (This constraint is deleted when the partition
|
* constraint. (For non-partitioned cases, relevant entries were created
|
||||||
* is detached, but a special check needs to occur that the partition
|
* by CreateConstraintEntry.)
|
||||||
* contains no referenced values.)
|
*
|
||||||
|
* On the referenced side, we need the constraint to have an internal
|
||||||
|
* dependency on its parent constraint; this means that this constraint
|
||||||
|
* cannot be dropped on its own -- only through the parent constraint. It
|
||||||
|
* also means the containing partition cannot be dropped on its own, but
|
||||||
|
* it can be detached, at which point this dependency is removed (after
|
||||||
|
* verifying that no rows are referenced via this FK.)
|
||||||
|
*
|
||||||
|
* When processing the referencing side, we link the constraint via the
|
||||||
|
* special partitioning dependencies: the parent constraint is the primary
|
||||||
|
* dependent, and the partition on which the foreign key exists is the
|
||||||
|
* secondary dependency. That way, this constraint is dropped if either
|
||||||
|
* of these objects is.
|
||||||
|
*
|
||||||
|
* Note that this is only necessary for the subsidiary pg_constraint rows
|
||||||
|
* in partitions; the topmost row doesn't need any of this.
|
||||||
*/
|
*/
|
||||||
if (OidIsValid(parentConstr))
|
if (OidIsValid(parentConstr))
|
||||||
{
|
{
|
||||||
ObjectAddress referenced;
|
ObjectAddress referenced;
|
||||||
|
|
||||||
ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
|
ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
|
||||||
recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
|
|
||||||
|
Assert(fkside != addFkBothSides);
|
||||||
|
if (fkside == addFkReferencedSide)
|
||||||
|
recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
|
||||||
|
ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
|
||||||
|
recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* make new constraint visible, in case we add more */
|
/* make new constraint visible, in case we add more */
|
||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* addFkRecurseReferenced
|
||||||
|
* Recursive helper for the referenced side of foreign key creation,
|
||||||
|
* which creates the action triggers and recurses
|
||||||
|
*
|
||||||
|
* If the referenced relation is a plain relation, create the necessary action
|
||||||
|
* triggers that implement the constraint. If the referenced relation is a
|
||||||
|
* partitioned table, then we create a pg_constraint row referencing the parent
|
||||||
|
* of the referencing side for it and recurse on this routine for each
|
||||||
|
* partition.
|
||||||
|
*
|
||||||
|
* fkconstraint: the constraint being added
|
||||||
|
* rel: the root referencing relation
|
||||||
|
* pkrel: the referenced relation; might be a partition, if recursing
|
||||||
|
* indexOid: the OID of the index (on pkrel) implementing this constraint
|
||||||
|
* parentConstr: the OID of a parent constraint; InvalidOid if this is a
|
||||||
|
* top-level constraint
|
||||||
|
* numfks: the number of columns in the foreign key
|
||||||
|
* pkattnum: the attnum array of referenced attributes
|
||||||
|
* fkattnum: the attnum array of referencing attributes
|
||||||
|
* numfkdelsetcols: the number of columns in the ON DELETE SET
|
||||||
|
* NULL/DEFAULT (...) clause
|
||||||
|
* fkdelsetcols: the attnum array of the columns in the ON DELETE SET
|
||||||
|
* NULL/DEFAULT clause
|
||||||
|
* pf/pp/ffeqoperators: OID array of operators between columns
|
||||||
|
* old_check_ok: true if this constraint replaces an existing one that
|
||||||
|
* was already validated (thus this one doesn't need validation)
|
||||||
|
* parentDelTrigger and parentUpdTrigger: when recursively called on a
|
||||||
|
* partition, the OIDs of the parent action triggers for DELETE and
|
||||||
|
* UPDATE respectively.
|
||||||
|
* with_period: true if this is a temporal FK
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
|
||||||
|
Relation pkrel, Oid indexOid, Oid parentConstr,
|
||||||
|
int numfks,
|
||||||
|
int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
|
||||||
|
Oid *ppeqoperators, Oid *ffeqoperators,
|
||||||
|
int numfkdelsetcols, int16 *fkdelsetcols,
|
||||||
|
bool old_check_ok,
|
||||||
|
Oid parentDelTrigger, Oid parentUpdTrigger,
|
||||||
|
bool with_period)
|
||||||
|
{
|
||||||
|
Oid deleteTriggerOid,
|
||||||
|
updateTriggerOid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the action triggers that enforce the constraint.
|
* Create the action triggers that enforce the constraint.
|
||||||
*/
|
*/
|
||||||
createForeignKeyActionTriggers(rel, RelationGetRelid(pkrel),
|
createForeignKeyActionTriggers(rel, RelationGetRelid(pkrel),
|
||||||
fkconstraint,
|
fkconstraint,
|
||||||
constrOid, indexOid,
|
parentConstr, indexOid,
|
||||||
parentDelTrigger, parentUpdTrigger,
|
parentDelTrigger, parentUpdTrigger,
|
||||||
&deleteTriggerOid, &updateTriggerOid);
|
&deleteTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
@ -10297,6 +10396,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
AttrMap *map;
|
AttrMap *map;
|
||||||
AttrNumber *mapped_pkattnum;
|
AttrNumber *mapped_pkattnum;
|
||||||
Oid partIndexId;
|
Oid partIndexId;
|
||||||
|
ObjectAddress address;
|
||||||
|
|
||||||
partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
|
partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
|
||||||
|
|
||||||
@ -10316,13 +10416,23 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
else
|
else
|
||||||
mapped_pkattnum = pkattnum;
|
mapped_pkattnum = pkattnum;
|
||||||
|
|
||||||
/* do the deed */
|
/* Determine the index to use at this level */
|
||||||
partIndexId = index_get_partition(partRel, indexOid);
|
partIndexId = index_get_partition(partRel, indexOid);
|
||||||
if (!OidIsValid(partIndexId))
|
if (!OidIsValid(partIndexId))
|
||||||
elog(ERROR, "index for %u not found in partition %s",
|
elog(ERROR, "index for %u not found in partition %s",
|
||||||
indexOid, RelationGetRelationName(partRel));
|
indexOid, RelationGetRelationName(partRel));
|
||||||
addFkRecurseReferenced(wqueue, fkconstraint, rel, partRel,
|
|
||||||
partIndexId, constrOid, numfks,
|
/* Create entry at this level ... */
|
||||||
|
address = addFkConstraint(addFkReferencedSide,
|
||||||
|
fkconstraint->conname, fkconstraint, rel,
|
||||||
|
partRel, partIndexId, parentConstr,
|
||||||
|
numfks, mapped_pkattnum,
|
||||||
|
fkattnum, pfeqoperators, ppeqoperators,
|
||||||
|
ffeqoperators, numfkdelsetcols,
|
||||||
|
fkdelsetcols, true, with_period);
|
||||||
|
/* ... and recurse to our children */
|
||||||
|
addFkRecurseReferenced(fkconstraint, rel, partRel,
|
||||||
|
partIndexId, address.objectId, numfks,
|
||||||
mapped_pkattnum, fkattnum,
|
mapped_pkattnum, fkattnum,
|
||||||
pfeqoperators, ppeqoperators, ffeqoperators,
|
pfeqoperators, ppeqoperators, ffeqoperators,
|
||||||
numfkdelsetcols, fkdelsetcols,
|
numfkdelsetcols, fkdelsetcols,
|
||||||
@ -10339,13 +10449,12 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* addFkRecurseReferencing
|
* addFkRecurseReferencing
|
||||||
* subroutine for ATAddForeignKeyConstraint and CloneFkReferencing
|
* Recursive helper for the referencing side of foreign key creation,
|
||||||
|
* which creates the check triggers and recurses
|
||||||
*
|
*
|
||||||
* If the referencing relation is a plain relation, create the necessary check
|
* If the referencing relation is a plain relation, create the necessary check
|
||||||
* triggers that implement the constraint, and set up for Phase 3 constraint
|
* triggers that implement the constraint, and set up for Phase 3 constraint
|
||||||
@ -10357,27 +10466,28 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
* deletions. If it's a partitioned relation, every partition must be so
|
* deletions. If it's a partitioned relation, every partition must be so
|
||||||
* locked.
|
* locked.
|
||||||
*
|
*
|
||||||
* wqueue is the ALTER TABLE work queue; can be NULL when not running as part
|
* wqueue: the ALTER TABLE work queue; NULL when not running as part
|
||||||
* of an ALTER TABLE sequence.
|
* of an ALTER TABLE sequence.
|
||||||
* fkconstraint is the constraint being added.
|
* fkconstraint: the constraint being added
|
||||||
* rel is the referencing relation; might be a partition, if recursing.
|
* rel: the referencing relation; might be a partition, if recursing
|
||||||
* pkrel is the root referenced relation.
|
* pkrel: the root referenced relation
|
||||||
* indexOid is the OID of the index (on pkrel) implementing this constraint.
|
* indexOid: the OID of the index (on pkrel) implementing this constraint
|
||||||
* parentConstr is the OID of the parent constraint (there is always one).
|
* parentConstr: the OID of the parent constraint (there is always one)
|
||||||
* numfks is the number of columns in the foreign key
|
* numfks: the number of columns in the foreign key
|
||||||
* pkattnum is the attnum array of referenced attributes.
|
* pkattnum: the attnum array of referenced attributes
|
||||||
* fkattnum is the attnum array of referencing attributes.
|
* fkattnum: the attnum array of referencing attributes
|
||||||
* pf/pp/ffeqoperators are OID array of operators between columns.
|
* pf/pp/ffeqoperators: OID array of operators between columns
|
||||||
* numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DEFAULT
|
* numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
|
||||||
* (...) clause
|
* (...) clause
|
||||||
* fkdelsetcols is the attnum array of the columns in the ON DELETE SET
|
* fkdelsetcols: the attnum array of the columns in the ON DELETE SET
|
||||||
* NULL/DEFAULT clause
|
* NULL/DEFAULT clause
|
||||||
* old_check_ok signals that this constraint replaces an existing one that
|
* old_check_ok: true if this constraint replaces an existing one that
|
||||||
* was already validated (thus this one doesn't need validation).
|
* was already validated (thus this one doesn't need validation)
|
||||||
* lockmode is the lockmode to acquire on partitions when recursing.
|
* lockmode: the lockmode to acquire on partitions when recursing
|
||||||
* parentInsTrigger and parentUpdTrigger, when being recursively called on
|
* parentInsTrigger and parentUpdTrigger: when being recursively called on
|
||||||
* a partition, are the OIDs of the parent check triggers for INSERT and
|
* a partition, the OIDs of the parent check triggers for INSERT and
|
||||||
* UPDATE respectively.
|
* UPDATE respectively.
|
||||||
|
* with_period: true if this is a temporal FK
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
||||||
@ -10467,10 +10577,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
AttrMap *attmap;
|
AttrMap *attmap;
|
||||||
AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
|
AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
|
||||||
bool attached;
|
bool attached;
|
||||||
char *conname;
|
ObjectAddress address;
|
||||||
Oid constrOid;
|
|
||||||
ObjectAddress address,
|
|
||||||
referenced;
|
|
||||||
ListCell *cell;
|
ListCell *cell;
|
||||||
|
|
||||||
CheckAlterTableIsSafe(partition);
|
CheckAlterTableIsSafe(partition);
|
||||||
@ -10513,66 +10620,19 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|||||||
/*
|
/*
|
||||||
* No luck finding a good constraint to reuse; create our own.
|
* No luck finding a good constraint to reuse; create our own.
|
||||||
*/
|
*/
|
||||||
if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
|
address = addFkConstraint(addFkReferencingSide,
|
||||||
RelationGetRelid(partition),
|
fkconstraint->conname, fkconstraint,
|
||||||
fkconstraint->conname))
|
partition, pkrel, indexOid, parentConstr,
|
||||||
conname = ChooseConstraintName(RelationGetRelationName(partition),
|
numfks, pkattnum,
|
||||||
ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
|
mapped_fkattnum, pfeqoperators,
|
||||||
"fkey",
|
ppeqoperators, ffeqoperators,
|
||||||
RelationGetNamespace(partition), NIL);
|
numfkdelsetcols, fkdelsetcols, true,
|
||||||
else
|
with_period);
|
||||||
conname = fkconstraint->conname;
|
|
||||||
constrOid =
|
|
||||||
CreateConstraintEntry(conname,
|
|
||||||
RelationGetNamespace(partition),
|
|
||||||
CONSTRAINT_FOREIGN,
|
|
||||||
fkconstraint->deferrable,
|
|
||||||
fkconstraint->initdeferred,
|
|
||||||
fkconstraint->initially_valid,
|
|
||||||
parentConstr,
|
|
||||||
partitionId,
|
|
||||||
mapped_fkattnum,
|
|
||||||
numfks,
|
|
||||||
numfks,
|
|
||||||
InvalidOid,
|
|
||||||
indexOid,
|
|
||||||
RelationGetRelid(pkrel),
|
|
||||||
pkattnum,
|
|
||||||
pfeqoperators,
|
|
||||||
ppeqoperators,
|
|
||||||
ffeqoperators,
|
|
||||||
numfks,
|
|
||||||
fkconstraint->fk_upd_action,
|
|
||||||
fkconstraint->fk_del_action,
|
|
||||||
fkdelsetcols,
|
|
||||||
numfkdelsetcols,
|
|
||||||
fkconstraint->fk_matchtype,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
false, /* conIsLocal */
|
|
||||||
1, /* conInhCount */
|
|
||||||
false, /* conNoInherit */
|
|
||||||
with_period, /* conPeriod */
|
|
||||||
false);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Give this constraint partition-type dependencies on the parent
|
|
||||||
* constraint as well as the table.
|
|
||||||
*/
|
|
||||||
ObjectAddressSet(address, ConstraintRelationId, constrOid);
|
|
||||||
ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
|
|
||||||
recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
|
|
||||||
ObjectAddressSet(referenced, RelationRelationId, partitionId);
|
|
||||||
recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
|
|
||||||
|
|
||||||
/* Make all this visible before recursing */
|
|
||||||
CommandCounterIncrement();
|
|
||||||
|
|
||||||
/* call ourselves to finalize the creation and we're done */
|
/* call ourselves to finalize the creation and we're done */
|
||||||
addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
|
addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
|
||||||
indexOid,
|
indexOid,
|
||||||
constrOid,
|
address.objectId,
|
||||||
numfks,
|
numfks,
|
||||||
pkattnum,
|
pkattnum,
|
||||||
mapped_fkattnum,
|
mapped_fkattnum,
|
||||||
@ -10706,6 +10766,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
int numfkdelsetcols;
|
int numfkdelsetcols;
|
||||||
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
||||||
Constraint *fkconstraint;
|
Constraint *fkconstraint;
|
||||||
|
ObjectAddress address;
|
||||||
Oid deleteTriggerOid,
|
Oid deleteTriggerOid,
|
||||||
updateTriggerOid;
|
updateTriggerOid;
|
||||||
|
|
||||||
@ -10739,7 +10800,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
* Because we're only expanding the key space at the referenced side,
|
* Because we're only expanding the key space at the referenced side,
|
||||||
* we don't need to prevent any operation in the referencing table, so
|
* we don't need to prevent any operation in the referencing table, so
|
||||||
* AccessShareLock suffices (assumes that dropping the constraint
|
* AccessShareLock suffices (assumes that dropping the constraint
|
||||||
* acquires AEL).
|
* acquires AccessExclusiveLock).
|
||||||
*/
|
*/
|
||||||
fkRel = table_open(constrForm->conrelid, AccessShareLock);
|
fkRel = table_open(constrForm->conrelid, AccessShareLock);
|
||||||
|
|
||||||
@ -10805,12 +10866,20 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
constrForm->confrelid, constrForm->conrelid,
|
constrForm->confrelid, constrForm->conrelid,
|
||||||
&deleteTriggerOid, &updateTriggerOid);
|
&deleteTriggerOid, &updateTriggerOid);
|
||||||
|
|
||||||
addFkRecurseReferenced(NULL,
|
/* Add this constraint ... */
|
||||||
fkconstraint,
|
address = addFkConstraint(addFkReferencedSide,
|
||||||
|
fkconstraint->conname, fkconstraint, fkRel,
|
||||||
|
partitionRel, partIndexId, constrOid,
|
||||||
|
numfks, mapped_confkey,
|
||||||
|
conkey, conpfeqop, conppeqop, conffeqop,
|
||||||
|
numfkdelsetcols, confdelsetcols, false,
|
||||||
|
constrForm->conperiod);
|
||||||
|
/* ... and recurse */
|
||||||
|
addFkRecurseReferenced(fkconstraint,
|
||||||
fkRel,
|
fkRel,
|
||||||
partitionRel,
|
partitionRel,
|
||||||
partIndexId,
|
partIndexId,
|
||||||
constrOid,
|
address.objectId,
|
||||||
numfks,
|
numfks,
|
||||||
mapped_confkey,
|
mapped_confkey,
|
||||||
conkey,
|
conkey,
|
||||||
@ -10841,8 +10910,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|||||||
* child.
|
* child.
|
||||||
*
|
*
|
||||||
* If wqueue is given, it is used to set up phase-3 verification for each
|
* If wqueue is given, it is used to set up phase-3 verification for each
|
||||||
* cloned constraint; if omitted, we assume that such verification is not
|
* cloned constraint; omit it if such verification is not needed
|
||||||
* needed (example: the partition is being created anew).
|
* (example: the partition is being created anew).
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
||||||
@ -10926,9 +10995,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
Constraint *fkconstraint;
|
Constraint *fkconstraint;
|
||||||
bool attached;
|
bool attached;
|
||||||
Oid indexOid;
|
Oid indexOid;
|
||||||
Oid constrOid;
|
ObjectAddress address;
|
||||||
ObjectAddress address,
|
|
||||||
referenced;
|
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
Oid insertTriggerOid,
|
Oid insertTriggerOid,
|
||||||
updateTriggerOid;
|
updateTriggerOid;
|
||||||
@ -11026,7 +11093,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
fkconstraint->old_conpfeqop = NIL;
|
fkconstraint->old_conpfeqop = NIL;
|
||||||
fkconstraint->old_pktable_oid = InvalidOid;
|
fkconstraint->old_pktable_oid = InvalidOid;
|
||||||
fkconstraint->skip_validation = false;
|
fkconstraint->skip_validation = false;
|
||||||
fkconstraint->initially_valid = true;
|
fkconstraint->initially_valid = constrForm->convalidated;
|
||||||
for (int i = 0; i < numfks; i++)
|
for (int i = 0; i < numfks; i++)
|
||||||
{
|
{
|
||||||
Form_pg_attribute att;
|
Form_pg_attribute att;
|
||||||
@ -11036,73 +11103,30 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|||||||
fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
|
fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
|
||||||
makeString(NameStr(att->attname)));
|
makeString(NameStr(att->attname)));
|
||||||
}
|
}
|
||||||
if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
|
|
||||||
RelationGetRelid(partRel),
|
|
||||||
NameStr(constrForm->conname)))
|
|
||||||
fkconstraint->conname =
|
|
||||||
ChooseConstraintName(RelationGetRelationName(partRel),
|
|
||||||
ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
|
|
||||||
"fkey",
|
|
||||||
RelationGetNamespace(partRel), NIL);
|
|
||||||
else
|
|
||||||
fkconstraint->conname = pstrdup(NameStr(constrForm->conname));
|
|
||||||
|
|
||||||
indexOid = constrForm->conindid;
|
indexOid = constrForm->conindid;
|
||||||
with_period = constrForm->conperiod;
|
with_period = constrForm->conperiod;
|
||||||
constrOid =
|
|
||||||
CreateConstraintEntry(fkconstraint->conname,
|
|
||||||
constrForm->connamespace,
|
|
||||||
CONSTRAINT_FOREIGN,
|
|
||||||
fkconstraint->deferrable,
|
|
||||||
fkconstraint->initdeferred,
|
|
||||||
constrForm->convalidated,
|
|
||||||
parentConstrOid,
|
|
||||||
RelationGetRelid(partRel),
|
|
||||||
mapped_conkey,
|
|
||||||
numfks,
|
|
||||||
numfks,
|
|
||||||
InvalidOid, /* not a domain constraint */
|
|
||||||
indexOid,
|
|
||||||
constrForm->confrelid, /* same foreign rel */
|
|
||||||
confkey,
|
|
||||||
conpfeqop,
|
|
||||||
conppeqop,
|
|
||||||
conffeqop,
|
|
||||||
numfks,
|
|
||||||
fkconstraint->fk_upd_action,
|
|
||||||
fkconstraint->fk_del_action,
|
|
||||||
confdelsetcols,
|
|
||||||
numfkdelsetcols,
|
|
||||||
fkconstraint->fk_matchtype,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
false, /* conIsLocal */
|
|
||||||
1, /* conInhCount */
|
|
||||||
false, /* conNoInherit */
|
|
||||||
with_period, /* conPeriod */
|
|
||||||
true);
|
|
||||||
|
|
||||||
/* Set up partition dependencies for the new constraint */
|
/* Create the pg_constraint entry at this level */
|
||||||
ObjectAddressSet(address, ConstraintRelationId, constrOid);
|
address = addFkConstraint(addFkReferencingSide,
|
||||||
ObjectAddressSet(referenced, ConstraintRelationId, parentConstrOid);
|
NameStr(constrForm->conname), fkconstraint,
|
||||||
recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
|
partRel, pkrel, indexOid, parentConstrOid,
|
||||||
ObjectAddressSet(referenced, RelationRelationId,
|
numfks, confkey,
|
||||||
RelationGetRelid(partRel));
|
mapped_conkey, conpfeqop,
|
||||||
recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
|
conppeqop, conffeqop,
|
||||||
|
numfkdelsetcols, confdelsetcols,
|
||||||
|
false, with_period);
|
||||||
|
|
||||||
/* Done with the cloned constraint's tuple */
|
/* Done with the cloned constraint's tuple */
|
||||||
ReleaseSysCache(tuple);
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
/* Make all this visible before recursing */
|
/* Create the check triggers, and recurse to partitions, if any */
|
||||||
CommandCounterIncrement();
|
|
||||||
|
|
||||||
addFkRecurseReferencing(wqueue,
|
addFkRecurseReferencing(wqueue,
|
||||||
fkconstraint,
|
fkconstraint,
|
||||||
partRel,
|
partRel,
|
||||||
pkrel,
|
pkrel,
|
||||||
indexOid,
|
indexOid,
|
||||||
constrOid,
|
address.objectId,
|
||||||
numfks,
|
numfks,
|
||||||
confkey,
|
confkey,
|
||||||
mapped_conkey,
|
mapped_conkey,
|
||||||
@ -11267,6 +11291,81 @@ tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
|
|||||||
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
|
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
|
||||||
partRelid);
|
partRelid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the referenced table is partitioned, then the partition we're
|
||||||
|
* attaching now has extra pg_constraint rows and action triggers that are
|
||||||
|
* no longer needed. Remove those.
|
||||||
|
*/
|
||||||
|
if (get_rel_relkind(fk->confrelid) == RELKIND_PARTITIONED_TABLE)
|
||||||
|
{
|
||||||
|
Relation pg_constraint = table_open(ConstraintRelationId, RowShareLock);
|
||||||
|
ObjectAddresses *objs;
|
||||||
|
HeapTuple consttup;
|
||||||
|
|
||||||
|
ScanKeyInit(&key,
|
||||||
|
Anum_pg_constraint_conrelid,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(fk->conrelid));
|
||||||
|
|
||||||
|
scan = systable_beginscan(pg_constraint,
|
||||||
|
ConstraintRelidTypidNameIndexId,
|
||||||
|
true, NULL, 1, &key);
|
||||||
|
objs = new_object_addresses();
|
||||||
|
while ((consttup = systable_getnext(scan)) != NULL)
|
||||||
|
{
|
||||||
|
Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
|
||||||
|
|
||||||
|
if (conform->conparentid != fk->conoid)
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ObjectAddress addr;
|
||||||
|
SysScanDesc scan2;
|
||||||
|
ScanKeyData key2;
|
||||||
|
int n PG_USED_FOR_ASSERTS_ONLY;
|
||||||
|
|
||||||
|
ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
|
||||||
|
add_exact_object_address(&addr, objs);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First we must delete the dependency record that binds the
|
||||||
|
* constraint records together.
|
||||||
|
*/
|
||||||
|
n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
|
||||||
|
conform->oid,
|
||||||
|
DEPENDENCY_INTERNAL,
|
||||||
|
ConstraintRelationId,
|
||||||
|
fk->conoid);
|
||||||
|
Assert(n == 1); /* actually only one is expected */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now search for the triggers for this constraint and set
|
||||||
|
* them up for deletion too
|
||||||
|
*/
|
||||||
|
ScanKeyInit(&key2,
|
||||||
|
Anum_pg_trigger_tgconstraint,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(conform->oid));
|
||||||
|
scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
|
||||||
|
true, NULL, 1, &key2);
|
||||||
|
while ((trigtup = systable_getnext(scan2)) != NULL)
|
||||||
|
{
|
||||||
|
ObjectAddressSet(addr, TriggerRelationId,
|
||||||
|
((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
|
||||||
|
add_exact_object_address(&addr, objs);
|
||||||
|
}
|
||||||
|
systable_endscan(scan2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* make the dependency deletions visible */
|
||||||
|
CommandCounterIncrement();
|
||||||
|
performMultipleDeletions(objs, DROP_RESTRICT,
|
||||||
|
PERFORM_DELETION_INTERNAL);
|
||||||
|
systable_endscan(scan);
|
||||||
|
|
||||||
|
table_close(pg_constraint, RowShareLock);
|
||||||
|
}
|
||||||
|
|
||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -19337,9 +19436,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
foreach(cell, fks)
|
foreach(cell, fks)
|
||||||
{
|
{
|
||||||
ForeignKeyCacheInfo *fk = lfirst(cell);
|
ForeignKeyCacheInfo *fk = lfirst(cell);
|
||||||
HeapTuple contup;
|
HeapTuple contup,
|
||||||
|
parentConTup;
|
||||||
Form_pg_constraint conform;
|
Form_pg_constraint conform;
|
||||||
Constraint *fkconstraint;
|
|
||||||
Oid insertTriggerOid,
|
Oid insertTriggerOid,
|
||||||
updateTriggerOid;
|
updateTriggerOid;
|
||||||
|
|
||||||
@ -19356,7 +19455,17 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* unset conparentid and adjust conislocal, coninhcount, etc. */
|
Assert(OidIsValid(conform->conparentid));
|
||||||
|
parentConTup = SearchSysCache1(CONSTROID,
|
||||||
|
ObjectIdGetDatum(conform->conparentid));
|
||||||
|
if (!HeapTupleIsValid(parentConTup))
|
||||||
|
elog(ERROR, "cache lookup failed for constraint %u",
|
||||||
|
conform->conparentid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The constraint on this table must be marked no longer a child of
|
||||||
|
* the parent's constraint, as do its check triggers.
|
||||||
|
*/
|
||||||
ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
|
ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -19374,35 +19483,93 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|||||||
RelationGetRelid(partRel));
|
RelationGetRelid(partRel));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make the action triggers on the referenced relation. When this was
|
* Lastly, create the action triggers on the referenced table, using
|
||||||
* a partition the action triggers pointed to the parent rel (they
|
* addFkRecurseReferenced, which requires some elaborate setup (so put
|
||||||
* still do), but now we need separate ones of our own.
|
* it in a separate block). While at it, if the table is partitioned,
|
||||||
|
* that function will recurse to create the pg_constraint rows and
|
||||||
|
* action triggers for each partition.
|
||||||
|
*
|
||||||
|
* Note there's no need to do addFkConstraint() here, because the
|
||||||
|
* pg_constraint row already exists.
|
||||||
*/
|
*/
|
||||||
fkconstraint = makeNode(Constraint);
|
{
|
||||||
fkconstraint->contype = CONSTRAINT_FOREIGN;
|
Constraint *fkconstraint;
|
||||||
fkconstraint->conname = pstrdup(NameStr(conform->conname));
|
int numfks;
|
||||||
fkconstraint->deferrable = conform->condeferrable;
|
AttrNumber conkey[INDEX_MAX_KEYS];
|
||||||
fkconstraint->initdeferred = conform->condeferred;
|
AttrNumber confkey[INDEX_MAX_KEYS];
|
||||||
fkconstraint->location = -1;
|
Oid conpfeqop[INDEX_MAX_KEYS];
|
||||||
fkconstraint->pktable = NULL;
|
Oid conppeqop[INDEX_MAX_KEYS];
|
||||||
fkconstraint->fk_attrs = NIL;
|
Oid conffeqop[INDEX_MAX_KEYS];
|
||||||
fkconstraint->pk_attrs = NIL;
|
int numfkdelsetcols;
|
||||||
fkconstraint->fk_matchtype = conform->confmatchtype;
|
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
||||||
fkconstraint->fk_upd_action = conform->confupdtype;
|
AttrMap *attmap;
|
||||||
fkconstraint->fk_del_action = conform->confdeltype;
|
Relation refdRel;
|
||||||
fkconstraint->fk_del_set_cols = NIL;
|
|
||||||
fkconstraint->old_conpfeqop = NIL;
|
|
||||||
fkconstraint->old_pktable_oid = InvalidOid;
|
|
||||||
fkconstraint->skip_validation = false;
|
|
||||||
fkconstraint->initially_valid = true;
|
|
||||||
|
|
||||||
createForeignKeyActionTriggers(partRel, conform->confrelid,
|
DeconstructFkConstraintRow(contup,
|
||||||
fkconstraint, fk->conoid,
|
&numfks,
|
||||||
conform->conindid,
|
conkey,
|
||||||
InvalidOid, InvalidOid,
|
confkey,
|
||||||
NULL, NULL);
|
conpfeqop,
|
||||||
|
conppeqop,
|
||||||
|
conffeqop,
|
||||||
|
&numfkdelsetcols,
|
||||||
|
confdelsetcols);
|
||||||
|
|
||||||
|
/* Create a synthetic node we'll use throughout */
|
||||||
|
fkconstraint = makeNode(Constraint);
|
||||||
|
fkconstraint->contype = CONSTRAINT_FOREIGN;
|
||||||
|
fkconstraint->conname = pstrdup(NameStr(conform->conname));
|
||||||
|
fkconstraint->deferrable = conform->condeferrable;
|
||||||
|
fkconstraint->initdeferred = conform->condeferred;
|
||||||
|
fkconstraint->skip_validation = true;
|
||||||
|
fkconstraint->initially_valid = true;
|
||||||
|
/* a few irrelevant fields omitted here */
|
||||||
|
fkconstraint->pktable = NULL;
|
||||||
|
fkconstraint->fk_attrs = NIL;
|
||||||
|
fkconstraint->pk_attrs = NIL;
|
||||||
|
fkconstraint->fk_matchtype = conform->confmatchtype;
|
||||||
|
fkconstraint->fk_upd_action = conform->confupdtype;
|
||||||
|
fkconstraint->fk_del_action = conform->confdeltype;
|
||||||
|
fkconstraint->fk_del_set_cols = NIL;
|
||||||
|
fkconstraint->old_conpfeqop = NIL;
|
||||||
|
fkconstraint->old_pktable_oid = InvalidOid;
|
||||||
|
fkconstraint->location = -1;
|
||||||
|
|
||||||
|
attmap = build_attrmap_by_name(RelationGetDescr(partRel),
|
||||||
|
RelationGetDescr(rel),
|
||||||
|
false);
|
||||||
|
for (int i = 0; i < numfks; i++)
|
||||||
|
{
|
||||||
|
Form_pg_attribute att;
|
||||||
|
|
||||||
|
att = TupleDescAttr(RelationGetDescr(partRel),
|
||||||
|
attmap->attnums[conkey[i] - 1] - 1);
|
||||||
|
fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
|
||||||
|
makeString(NameStr(att->attname)));
|
||||||
|
}
|
||||||
|
|
||||||
|
refdRel = table_open(fk->confrelid, AccessShareLock);
|
||||||
|
|
||||||
|
addFkRecurseReferenced(fkconstraint, partRel,
|
||||||
|
refdRel,
|
||||||
|
conform->conindid,
|
||||||
|
fk->conoid,
|
||||||
|
numfks,
|
||||||
|
confkey,
|
||||||
|
conkey,
|
||||||
|
conpfeqop,
|
||||||
|
conppeqop,
|
||||||
|
conffeqop,
|
||||||
|
numfkdelsetcols,
|
||||||
|
confdelsetcols,
|
||||||
|
true,
|
||||||
|
InvalidOid, InvalidOid,
|
||||||
|
conform->conperiod);
|
||||||
|
table_close(refdRel, AccessShareLock);
|
||||||
|
}
|
||||||
|
|
||||||
ReleaseSysCache(contup);
|
ReleaseSysCache(contup);
|
||||||
|
ReleaseSysCache(parentConTup);
|
||||||
}
|
}
|
||||||
list_free_deep(fks);
|
list_free_deep(fks);
|
||||||
if (trigrel)
|
if (trigrel)
|
||||||
|
@ -2934,3 +2934,99 @@ DETAIL: drop cascades to table fkpart11.pk
|
|||||||
drop cascades to table fkpart11.fk_parted
|
drop cascades to table fkpart11.fk_parted
|
||||||
drop cascades to table fkpart11.fk_another
|
drop cascades to table fkpart11.fk_another
|
||||||
drop cascades to function fkpart11.print_row()
|
drop cascades to function fkpart11.print_row()
|
||||||
|
-- When a table is attached as partition to a partitioned table that has
|
||||||
|
-- a foreign key to another partitioned table, it acquires a clone of the
|
||||||
|
-- FK. Upon detach, this clone is not removed, but instead becomes an
|
||||||
|
-- independent FK. If it then attaches to the partitioned table again,
|
||||||
|
-- the FK from the parent "takes over" ownership of the independent FK rather
|
||||||
|
-- than creating a separate one.
|
||||||
|
CREATE SCHEMA fkpart12
|
||||||
|
CREATE TABLE fk_p ( id int, jd int, PRIMARY KEY(id, jd)) PARTITION BY list (id)
|
||||||
|
CREATE TABLE fk_p_1 PARTITION OF fk_p FOR VALUES IN (1) PARTITION BY list (jd)
|
||||||
|
CREATE TABLE fk_p_1_1 PARTITION OF fk_p_1 FOR VALUES IN (1)
|
||||||
|
CREATE TABLE fk_p_1_2 PARTITION OF fk_p_1 FOR VALUES IN (2)
|
||||||
|
CREATE TABLE fk_p_2 PARTITION OF fk_p FOR VALUES IN (2) PARTITION BY list (jd)
|
||||||
|
CREATE TABLE fk_p_2_1 PARTITION OF fk_p_2 FOR VALUES IN (1)
|
||||||
|
CREATE TABLE fk_p_2_2 PARTITION OF fk_p_2 FOR VALUES IN (2)
|
||||||
|
CREATE TABLE fk_r_1 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL)
|
||||||
|
CREATE TABLE fk_r_2 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL) PARTITION BY list (id)
|
||||||
|
CREATE TABLE fk_r_2_1 PARTITION OF fk_r_2 FOR VALUES IN (2, 1)
|
||||||
|
CREATE TABLE fk_r ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL,
|
||||||
|
FOREIGN KEY (p_id, p_jd) REFERENCES fk_p (id, jd)
|
||||||
|
) PARTITION BY list (id);
|
||||||
|
SET search_path TO fkpart12;
|
||||||
|
INSERT INTO fk_p VALUES (1, 1);
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_1 FOR VALUES IN (1);
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_2 FOR VALUES IN (2);
|
||||||
|
\d fk_r_2
|
||||||
|
Partitioned table "fkpart12.fk_r_2"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
id | integer | | not null |
|
||||||
|
p_id | integer | | not null |
|
||||||
|
p_jd | integer | | not null |
|
||||||
|
Partition of: fk_r FOR VALUES IN (2)
|
||||||
|
Partition key: LIST (id)
|
||||||
|
Indexes:
|
||||||
|
"fk_r_2_pkey" PRIMARY KEY, btree (id)
|
||||||
|
Foreign-key constraints:
|
||||||
|
TABLE "fk_r" CONSTRAINT "fk_r_p_id_p_jd_fkey" FOREIGN KEY (p_id, p_jd) REFERENCES fk_p(id, jd)
|
||||||
|
Number of partitions: 1 (Use \d+ to list them.)
|
||||||
|
|
||||||
|
INSERT INTO fk_r VALUES (1, 1, 1);
|
||||||
|
INSERT INTO fk_r VALUES (2, 2, 1);
|
||||||
|
ERROR: insert or update on table "fk_r_2_1" violates foreign key constraint "fk_r_p_id_p_jd_fkey"
|
||||||
|
DETAIL: Key (p_id, p_jd)=(2, 1) is not present in table "fk_p".
|
||||||
|
ALTER TABLE fk_r DETACH PARTITION fk_r_1;
|
||||||
|
ALTER TABLE fk_r DETACH PARTITION fk_r_2;
|
||||||
|
\d fk_r_2
|
||||||
|
Partitioned table "fkpart12.fk_r_2"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
id | integer | | not null |
|
||||||
|
p_id | integer | | not null |
|
||||||
|
p_jd | integer | | not null |
|
||||||
|
Partition key: LIST (id)
|
||||||
|
Indexes:
|
||||||
|
"fk_r_2_pkey" PRIMARY KEY, btree (id)
|
||||||
|
Foreign-key constraints:
|
||||||
|
"fk_r_p_id_p_jd_fkey" FOREIGN KEY (p_id, p_jd) REFERENCES fk_p(id, jd)
|
||||||
|
Number of partitions: 1 (Use \d+ to list them.)
|
||||||
|
|
||||||
|
INSERT INTO fk_r_1 VALUES (2, 1, 2); -- should fail
|
||||||
|
ERROR: insert or update on table "fk_r_1" violates foreign key constraint "fk_r_p_id_p_jd_fkey"
|
||||||
|
DETAIL: Key (p_id, p_jd)=(1, 2) is not present in table "fk_p".
|
||||||
|
DELETE FROM fk_p; -- should fail
|
||||||
|
ERROR: update or delete on table "fk_p_1_1" violates foreign key constraint "fk_r_1_p_id_p_jd_fkey1" on table "fk_r_1"
|
||||||
|
DETAIL: Key (id, jd)=(1, 1) is still referenced from table "fk_r_1".
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_1 FOR VALUES IN (1);
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_2 FOR VALUES IN (2);
|
||||||
|
\d fk_r_2
|
||||||
|
Partitioned table "fkpart12.fk_r_2"
|
||||||
|
Column | Type | Collation | Nullable | Default
|
||||||
|
--------+---------+-----------+----------+---------
|
||||||
|
id | integer | | not null |
|
||||||
|
p_id | integer | | not null |
|
||||||
|
p_jd | integer | | not null |
|
||||||
|
Partition of: fk_r FOR VALUES IN (2)
|
||||||
|
Partition key: LIST (id)
|
||||||
|
Indexes:
|
||||||
|
"fk_r_2_pkey" PRIMARY KEY, btree (id)
|
||||||
|
Foreign-key constraints:
|
||||||
|
TABLE "fk_r" CONSTRAINT "fk_r_p_id_p_jd_fkey" FOREIGN KEY (p_id, p_jd) REFERENCES fk_p(id, jd)
|
||||||
|
Number of partitions: 1 (Use \d+ to list them.)
|
||||||
|
|
||||||
|
DELETE FROM fk_p; -- should fail
|
||||||
|
ERROR: update or delete on table "fk_p_1_1" violates foreign key constraint "fk_r_p_id_p_jd_fkey2" on table "fk_r"
|
||||||
|
DETAIL: Key (id, jd)=(1, 1) is still referenced from table "fk_r".
|
||||||
|
-- these should all fail
|
||||||
|
ALTER TABLE fk_r_1 DROP CONSTRAINT fk_r_p_id_p_jd_fkey;
|
||||||
|
ERROR: cannot drop inherited constraint "fk_r_p_id_p_jd_fkey" of relation "fk_r_1"
|
||||||
|
ALTER TABLE fk_r DROP CONSTRAINT fk_r_p_id_p_jd_fkey1;
|
||||||
|
ERROR: cannot drop inherited constraint "fk_r_p_id_p_jd_fkey1" of relation "fk_r"
|
||||||
|
ALTER TABLE fk_r_2 DROP CONSTRAINT fk_r_p_id_p_jd_fkey;
|
||||||
|
ERROR: cannot drop inherited constraint "fk_r_p_id_p_jd_fkey" of relation "fk_r_2"
|
||||||
|
SET client_min_messages TO warning;
|
||||||
|
DROP SCHEMA fkpart12 CASCADE;
|
||||||
|
RESET client_min_messages;
|
||||||
|
RESET search_path;
|
||||||
|
@ -2086,3 +2086,60 @@ UPDATE fkpart11.pk SET a = 3 WHERE a = 4;
|
|||||||
UPDATE fkpart11.pk SET a = 1 WHERE a = 2;
|
UPDATE fkpart11.pk SET a = 1 WHERE a = 2;
|
||||||
|
|
||||||
DROP SCHEMA fkpart11 CASCADE;
|
DROP SCHEMA fkpart11 CASCADE;
|
||||||
|
|
||||||
|
-- When a table is attached as partition to a partitioned table that has
|
||||||
|
-- a foreign key to another partitioned table, it acquires a clone of the
|
||||||
|
-- FK. Upon detach, this clone is not removed, but instead becomes an
|
||||||
|
-- independent FK. If it then attaches to the partitioned table again,
|
||||||
|
-- the FK from the parent "takes over" ownership of the independent FK rather
|
||||||
|
-- than creating a separate one.
|
||||||
|
CREATE SCHEMA fkpart12
|
||||||
|
CREATE TABLE fk_p ( id int, jd int, PRIMARY KEY(id, jd)) PARTITION BY list (id)
|
||||||
|
CREATE TABLE fk_p_1 PARTITION OF fk_p FOR VALUES IN (1) PARTITION BY list (jd)
|
||||||
|
CREATE TABLE fk_p_1_1 PARTITION OF fk_p_1 FOR VALUES IN (1)
|
||||||
|
CREATE TABLE fk_p_1_2 PARTITION OF fk_p_1 FOR VALUES IN (2)
|
||||||
|
CREATE TABLE fk_p_2 PARTITION OF fk_p FOR VALUES IN (2) PARTITION BY list (jd)
|
||||||
|
CREATE TABLE fk_p_2_1 PARTITION OF fk_p_2 FOR VALUES IN (1)
|
||||||
|
CREATE TABLE fk_p_2_2 PARTITION OF fk_p_2 FOR VALUES IN (2)
|
||||||
|
CREATE TABLE fk_r_1 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL)
|
||||||
|
CREATE TABLE fk_r_2 ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL) PARTITION BY list (id)
|
||||||
|
CREATE TABLE fk_r_2_1 PARTITION OF fk_r_2 FOR VALUES IN (2, 1)
|
||||||
|
CREATE TABLE fk_r ( id int PRIMARY KEY, p_id int NOT NULL, p_jd int NOT NULL,
|
||||||
|
FOREIGN KEY (p_id, p_jd) REFERENCES fk_p (id, jd)
|
||||||
|
) PARTITION BY list (id);
|
||||||
|
SET search_path TO fkpart12;
|
||||||
|
|
||||||
|
INSERT INTO fk_p VALUES (1, 1);
|
||||||
|
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_1 FOR VALUES IN (1);
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_2 FOR VALUES IN (2);
|
||||||
|
|
||||||
|
\d fk_r_2
|
||||||
|
|
||||||
|
INSERT INTO fk_r VALUES (1, 1, 1);
|
||||||
|
INSERT INTO fk_r VALUES (2, 2, 1);
|
||||||
|
|
||||||
|
ALTER TABLE fk_r DETACH PARTITION fk_r_1;
|
||||||
|
ALTER TABLE fk_r DETACH PARTITION fk_r_2;
|
||||||
|
|
||||||
|
\d fk_r_2
|
||||||
|
|
||||||
|
INSERT INTO fk_r_1 VALUES (2, 1, 2); -- should fail
|
||||||
|
DELETE FROM fk_p; -- should fail
|
||||||
|
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_1 FOR VALUES IN (1);
|
||||||
|
ALTER TABLE fk_r ATTACH PARTITION fk_r_2 FOR VALUES IN (2);
|
||||||
|
|
||||||
|
\d fk_r_2
|
||||||
|
|
||||||
|
DELETE FROM fk_p; -- should fail
|
||||||
|
|
||||||
|
-- these should all fail
|
||||||
|
ALTER TABLE fk_r_1 DROP CONSTRAINT fk_r_p_id_p_jd_fkey;
|
||||||
|
ALTER TABLE fk_r DROP CONSTRAINT fk_r_p_id_p_jd_fkey1;
|
||||||
|
ALTER TABLE fk_r_2 DROP CONSTRAINT fk_r_p_id_p_jd_fkey;
|
||||||
|
|
||||||
|
SET client_min_messages TO warning;
|
||||||
|
DROP SCHEMA fkpart12 CASCADE;
|
||||||
|
RESET client_min_messages;
|
||||||
|
RESET search_path;
|
||||||
|
@ -3271,6 +3271,7 @@ _stringlist
|
|||||||
access_vector_t
|
access_vector_t
|
||||||
acquireLocksOnSubLinks_context
|
acquireLocksOnSubLinks_context
|
||||||
add_nulling_relids_context
|
add_nulling_relids_context
|
||||||
|
addFkConstraintSides
|
||||||
adjust_appendrel_attrs_context
|
adjust_appendrel_attrs_context
|
||||||
allocfunc
|
allocfunc
|
||||||
amadjustmembers_function
|
amadjustmembers_function
|
||||||
|
Loading…
x
Reference in New Issue
Block a user