|
|
|
|
@@ -395,6 +395,14 @@ static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
|
|
|
|
|
static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
|
|
|
|
|
Relation tgrel, Relation rel, HeapTuple contuple,
|
|
|
|
|
bool recurse, LOCKMODE lockmode);
|
|
|
|
|
static bool ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
Relation conrel, Relation tgrel,
|
|
|
|
|
const Oid fkrelid, const Oid pkrelid,
|
|
|
|
|
HeapTuple contuple, LOCKMODE lockmode,
|
|
|
|
|
Oid ReferencedParentDelTrigger,
|
|
|
|
|
Oid ReferencedParentUpdTrigger,
|
|
|
|
|
Oid ReferencingParentInsTrigger,
|
|
|
|
|
Oid ReferencingParentUpdTrigger);
|
|
|
|
|
static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
Relation conrel, Relation tgrel, Relation rel,
|
|
|
|
|
HeapTuple contuple, bool recurse,
|
|
|
|
|
@@ -405,6 +413,14 @@ static bool ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cm
|
|
|
|
|
static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
|
|
|
|
|
bool deferrable, bool initdeferred,
|
|
|
|
|
List **otherrelids);
|
|
|
|
|
static void AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
Relation conrel, Relation tgrel,
|
|
|
|
|
const Oid fkrelid, const Oid pkrelid,
|
|
|
|
|
HeapTuple contuple, LOCKMODE lockmode,
|
|
|
|
|
Oid ReferencedParentDelTrigger,
|
|
|
|
|
Oid ReferencedParentUpdTrigger,
|
|
|
|
|
Oid ReferencingParentInsTrigger,
|
|
|
|
|
Oid ReferencingParentUpdTrigger);
|
|
|
|
|
static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
Relation conrel, Relation tgrel, Relation rel,
|
|
|
|
|
HeapTuple contuple, bool recurse,
|
|
|
|
|
@@ -10610,7 +10626,7 @@ addFkConstraint(addFkConstraintSides fkside,
|
|
|
|
|
CONSTRAINT_FOREIGN,
|
|
|
|
|
fkconstraint->deferrable,
|
|
|
|
|
fkconstraint->initdeferred,
|
|
|
|
|
true, /* Is Enforced */
|
|
|
|
|
fkconstraint->is_enforced,
|
|
|
|
|
fkconstraint->initially_valid,
|
|
|
|
|
parentConstr,
|
|
|
|
|
RelationGetRelid(rel),
|
|
|
|
|
@@ -10728,21 +10744,23 @@ addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
|
|
|
|
|
Oid parentDelTrigger, Oid parentUpdTrigger,
|
|
|
|
|
bool with_period)
|
|
|
|
|
{
|
|
|
|
|
Oid deleteTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
Oid deleteTriggerOid = InvalidOid,
|
|
|
|
|
updateTriggerOid = InvalidOid;
|
|
|
|
|
|
|
|
|
|
Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
|
|
|
|
|
Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create the action triggers that enforce the constraint.
|
|
|
|
|
* Create action triggers to enforce the constraint, or skip them if the
|
|
|
|
|
* constraint is NOT ENFORCED.
|
|
|
|
|
*/
|
|
|
|
|
createForeignKeyActionTriggers(RelationGetRelid(rel),
|
|
|
|
|
RelationGetRelid(pkrel),
|
|
|
|
|
fkconstraint,
|
|
|
|
|
parentConstr, indexOid,
|
|
|
|
|
parentDelTrigger, parentUpdTrigger,
|
|
|
|
|
&deleteTriggerOid, &updateTriggerOid);
|
|
|
|
|
if (fkconstraint->is_enforced)
|
|
|
|
|
createForeignKeyActionTriggers(RelationGetRelid(rel),
|
|
|
|
|
RelationGetRelid(pkrel),
|
|
|
|
|
fkconstraint,
|
|
|
|
|
parentConstr, indexOid,
|
|
|
|
|
parentDelTrigger, parentUpdTrigger,
|
|
|
|
|
&deleteTriggerOid, &updateTriggerOid);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the referenced table is partitioned, recurse on ourselves to handle
|
|
|
|
|
@@ -10863,8 +10881,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|
|
|
|
Oid parentInsTrigger, Oid parentUpdTrigger,
|
|
|
|
|
bool with_period)
|
|
|
|
|
{
|
|
|
|
|
Oid insertTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
Oid insertTriggerOid = InvalidOid,
|
|
|
|
|
updateTriggerOid = InvalidOid;
|
|
|
|
|
|
|
|
|
|
Assert(OidIsValid(parentConstr));
|
|
|
|
|
Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
|
|
|
|
|
@@ -10876,29 +10894,32 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
|
|
|
|
errmsg("foreign key constraints are not supported on foreign tables")));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Add the check triggers to it and, if necessary, schedule it to be
|
|
|
|
|
* checked in Phase 3.
|
|
|
|
|
* Add check triggers if the constraint is ENFORCED, and if needed,
|
|
|
|
|
* schedule them to be checked in Phase 3.
|
|
|
|
|
*
|
|
|
|
|
* If the relation is partitioned, drill down to do it to its partitions.
|
|
|
|
|
*/
|
|
|
|
|
createForeignKeyCheckTriggers(RelationGetRelid(rel),
|
|
|
|
|
RelationGetRelid(pkrel),
|
|
|
|
|
fkconstraint,
|
|
|
|
|
parentConstr,
|
|
|
|
|
indexOid,
|
|
|
|
|
parentInsTrigger, parentUpdTrigger,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
if (fkconstraint->is_enforced)
|
|
|
|
|
createForeignKeyCheckTriggers(RelationGetRelid(rel),
|
|
|
|
|
RelationGetRelid(pkrel),
|
|
|
|
|
fkconstraint,
|
|
|
|
|
parentConstr,
|
|
|
|
|
indexOid,
|
|
|
|
|
parentInsTrigger, parentUpdTrigger,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
|
|
|
|
|
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Tell Phase 3 to check that the constraint is satisfied by existing
|
|
|
|
|
* rows. We can skip this during table creation, when requested
|
|
|
|
|
* explicitly by specifying NOT VALID in an ADD FOREIGN KEY command,
|
|
|
|
|
* and when we're recreating a constraint following a SET DATA TYPE
|
|
|
|
|
* operation that did not impugn its validity.
|
|
|
|
|
* rows. We can skip this during table creation, when constraint is
|
|
|
|
|
* specified as NOT ENFORCED, or when requested explicitly by
|
|
|
|
|
* specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
|
|
|
|
|
* recreating a constraint following a SET DATA TYPE operation that
|
|
|
|
|
* did not impugn its validity.
|
|
|
|
|
*/
|
|
|
|
|
if (wqueue && !old_check_ok && !fkconstraint->skip_validation)
|
|
|
|
|
if (wqueue && !old_check_ok && !fkconstraint->skip_validation &&
|
|
|
|
|
fkconstraint->is_enforced)
|
|
|
|
|
{
|
|
|
|
|
NewConstraint *newcon;
|
|
|
|
|
AlteredTableInfo *tab;
|
|
|
|
|
@@ -11129,8 +11150,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|
|
|
|
AttrNumber confdelsetcols[INDEX_MAX_KEYS];
|
|
|
|
|
Constraint *fkconstraint;
|
|
|
|
|
ObjectAddress address;
|
|
|
|
|
Oid deleteTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
Oid deleteTriggerOid = InvalidOid,
|
|
|
|
|
updateTriggerOid = InvalidOid;
|
|
|
|
|
|
|
|
|
|
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
|
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
|
@@ -11190,8 +11211,9 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|
|
|
|
fkconstraint->fk_del_set_cols = NIL;
|
|
|
|
|
fkconstraint->old_conpfeqop = NIL;
|
|
|
|
|
fkconstraint->old_pktable_oid = InvalidOid;
|
|
|
|
|
fkconstraint->is_enforced = constrForm->conenforced;
|
|
|
|
|
fkconstraint->skip_validation = false;
|
|
|
|
|
fkconstraint->initially_valid = true;
|
|
|
|
|
fkconstraint->initially_valid = constrForm->convalidated;
|
|
|
|
|
|
|
|
|
|
/* set up colnames that are used to generate the constraint name */
|
|
|
|
|
for (int i = 0; i < numfks; i++)
|
|
|
|
|
@@ -11219,9 +11241,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
|
|
|
|
|
* parent OIDs for similar triggers that will be created on the
|
|
|
|
|
* partition in addFkRecurseReferenced().
|
|
|
|
|
*/
|
|
|
|
|
GetForeignKeyActionTriggers(trigrel, constrOid,
|
|
|
|
|
constrForm->confrelid, constrForm->conrelid,
|
|
|
|
|
&deleteTriggerOid, &updateTriggerOid);
|
|
|
|
|
if (constrForm->conenforced)
|
|
|
|
|
GetForeignKeyActionTriggers(trigrel, constrOid,
|
|
|
|
|
constrForm->confrelid, constrForm->conrelid,
|
|
|
|
|
&deleteTriggerOid, &updateTriggerOid);
|
|
|
|
|
|
|
|
|
|
/* Add this constraint ... */
|
|
|
|
|
address = addFkConstraint(addFkReferencedSide,
|
|
|
|
|
@@ -11354,8 +11377,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|
|
|
|
Oid indexOid;
|
|
|
|
|
ObjectAddress address;
|
|
|
|
|
ListCell *lc;
|
|
|
|
|
Oid insertTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
Oid insertTriggerOid = InvalidOid,
|
|
|
|
|
updateTriggerOid = InvalidOid;
|
|
|
|
|
bool with_period;
|
|
|
|
|
|
|
|
|
|
tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
|
|
|
|
|
@@ -11387,17 +11410,18 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|
|
|
|
mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Get the "check" triggers belonging to the constraint to pass as
|
|
|
|
|
* parent OIDs for similar triggers that will be created on the
|
|
|
|
|
* partition in addFkRecurseReferencing(). They are also passed to
|
|
|
|
|
* tryAttachPartitionForeignKey() below to simply assign as parents to
|
|
|
|
|
* the partition's existing "check" triggers, that is, if the
|
|
|
|
|
* corresponding constraints is deemed attachable to the parent
|
|
|
|
|
* constraint.
|
|
|
|
|
* Get the "check" triggers belonging to the constraint, if it is
|
|
|
|
|
* ENFORCED, to pass as parent OIDs for similar triggers that will be
|
|
|
|
|
* created on the partition in addFkRecurseReferencing(). They are
|
|
|
|
|
* also passed to tryAttachPartitionForeignKey() below to simply
|
|
|
|
|
* assign as parents to the partition's existing "check" triggers,
|
|
|
|
|
* that is, if the corresponding constraints is deemed attachable to
|
|
|
|
|
* the parent constraint.
|
|
|
|
|
*/
|
|
|
|
|
GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
|
|
|
|
|
constrForm->confrelid, constrForm->conrelid,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
if (constrForm->conenforced)
|
|
|
|
|
GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
|
|
|
|
|
constrForm->confrelid, constrForm->conrelid,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Before creating a new constraint, see whether any existing FKs are
|
|
|
|
|
@@ -11450,6 +11474,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
|
|
|
|
fkconstraint->fk_del_set_cols = NIL;
|
|
|
|
|
fkconstraint->old_conpfeqop = NIL;
|
|
|
|
|
fkconstraint->old_pktable_oid = InvalidOid;
|
|
|
|
|
fkconstraint->is_enforced = constrForm->conenforced;
|
|
|
|
|
fkconstraint->skip_validation = false;
|
|
|
|
|
fkconstraint->initially_valid = constrForm->convalidated;
|
|
|
|
|
for (int i = 0; i < numfks; i++)
|
|
|
|
|
@@ -11564,6 +11589,23 @@ tryAttachPartitionForeignKey(List **wqueue,
|
|
|
|
|
if (!HeapTupleIsValid(partcontup))
|
|
|
|
|
elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
|
|
|
|
|
partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* An error should be raised if the constraint enforceability is
|
|
|
|
|
* different. Returning false without raising an error, as we do for other
|
|
|
|
|
* attributes, could lead to a duplicate constraint with the same
|
|
|
|
|
* enforceability as the parent. While this may be acceptable, it may not
|
|
|
|
|
* be ideal. Therefore, it's better to raise an error and allow the user
|
|
|
|
|
* to correct the enforceability before proceeding.
|
|
|
|
|
*/
|
|
|
|
|
if (partConstr->conenforced != parentConstr->conenforced)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
|
|
|
errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
|
|
|
|
|
NameStr(parentConstr->conname),
|
|
|
|
|
NameStr(partConstr->conname),
|
|
|
|
|
RelationGetRelationName(partition))));
|
|
|
|
|
|
|
|
|
|
if (OidIsValid(partConstr->conparentid) ||
|
|
|
|
|
partConstr->condeferrable != parentConstr->condeferrable ||
|
|
|
|
|
partConstr->condeferred != parentConstr->condeferred ||
|
|
|
|
|
@@ -11610,8 +11652,7 @@ AttachPartitionForeignKey(List **wqueue,
|
|
|
|
|
bool queueValidation;
|
|
|
|
|
Oid partConstrFrelid;
|
|
|
|
|
Oid partConstrRelid;
|
|
|
|
|
Oid insertTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
bool parentConstrIsEnforced;
|
|
|
|
|
|
|
|
|
|
/* Fetch the parent constraint tuple */
|
|
|
|
|
parentConstrTup = SearchSysCache1(CONSTROID,
|
|
|
|
|
@@ -11619,6 +11660,7 @@ AttachPartitionForeignKey(List **wqueue,
|
|
|
|
|
if (!HeapTupleIsValid(parentConstrTup))
|
|
|
|
|
elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
|
|
|
|
|
parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
|
|
|
|
|
parentConstrIsEnforced = parentConstr->conenforced;
|
|
|
|
|
|
|
|
|
|
/* Fetch the child constraint tuple */
|
|
|
|
|
partcontup = SearchSysCache1(CONSTROID,
|
|
|
|
|
@@ -11668,17 +11710,24 @@ AttachPartitionForeignKey(List **wqueue,
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Like the constraint, attach partition's "check" triggers to the
|
|
|
|
|
* corresponding parent triggers.
|
|
|
|
|
* corresponding parent triggers if the constraint is ENFORCED. NOT
|
|
|
|
|
* ENFORCED constraints do not have these triggers.
|
|
|
|
|
*/
|
|
|
|
|
GetForeignKeyCheckTriggers(trigrel,
|
|
|
|
|
partConstrOid, partConstrFrelid, partConstrRelid,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
|
|
|
|
|
RelationGetRelid(partition));
|
|
|
|
|
Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
|
|
|
|
|
RelationGetRelid(partition));
|
|
|
|
|
if (parentConstrIsEnforced)
|
|
|
|
|
{
|
|
|
|
|
Oid insertTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
|
|
|
|
|
GetForeignKeyCheckTriggers(trigrel,
|
|
|
|
|
partConstrOid, partConstrFrelid, partConstrRelid,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
|
|
|
|
|
RelationGetRelid(partition));
|
|
|
|
|
Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
|
|
|
|
|
RelationGetRelid(partition));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We updated this pg_constraint row above to set its parent; validating
|
|
|
|
|
@@ -11792,6 +11841,10 @@ RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
|
|
|
|
|
*
|
|
|
|
|
* The subroutine for tryAttachPartitionForeignKey handles the deletion of
|
|
|
|
|
* action triggers for the foreign key constraint.
|
|
|
|
|
*
|
|
|
|
|
* If valid confrelid and conrelid values are not provided, the respective
|
|
|
|
|
* trigger check will be skipped, and the trigger will be considered for
|
|
|
|
|
* removal.
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
|
|
|
|
|
@@ -11812,10 +11865,27 @@ DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
|
|
|
|
|
Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
|
|
|
|
|
ObjectAddress trigger;
|
|
|
|
|
|
|
|
|
|
if (trgform->tgconstrrelid != conrelid)
|
|
|
|
|
/* Invalid if trigger is not for a referential integrity constraint */
|
|
|
|
|
if (!OidIsValid(trgform->tgconstrrelid))
|
|
|
|
|
continue;
|
|
|
|
|
if (trgform->tgrelid != confrelid)
|
|
|
|
|
if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
|
|
|
|
|
continue;
|
|
|
|
|
if (OidIsValid(confrelid) && trgform->tgrelid != confrelid)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/* We should be droping trigger related to foreign key constraint */
|
|
|
|
|
Assert(trgform->tgfoid == F_RI_FKEY_CHECK_INS ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_CHECK_UPD ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_CASCADE_DEL ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_CASCADE_UPD ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_RESTRICT_DEL ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_RESTRICT_UPD ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_SETNULL_DEL ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_SETNULL_UPD ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_SETDEFAULT_DEL ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_SETDEFAULT_UPD ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_NOACTION_DEL ||
|
|
|
|
|
trgform->tgfoid == F_RI_FKEY_NOACTION_UPD);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The constraint is originally set up to contain this trigger as an
|
|
|
|
|
@@ -12028,6 +12098,11 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
|
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
|
|
|
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
|
|
|
|
|
cmdcon->conname, RelationGetRelationName(rel))));
|
|
|
|
|
if (cmdcon->alterEnforceability && currcon->contype != CONSTRAINT_FOREIGN)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
|
|
|
errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
|
|
|
|
|
cmdcon->conname, RelationGetRelationName(rel))));
|
|
|
|
|
if (cmdcon->alterInheritability &&
|
|
|
|
|
currcon->contype != CONSTRAINT_NOTNULL)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
@@ -12107,7 +12182,7 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* A subroutine of ATExecAlterConstraint that calls the respective routines for
|
|
|
|
|
* altering constraint attributes.
|
|
|
|
|
* altering constraint's enforceability, deferrability or inheritability.
|
|
|
|
|
*/
|
|
|
|
|
static bool
|
|
|
|
|
ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
@@ -12115,16 +12190,35 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
HeapTuple contuple, bool recurse,
|
|
|
|
|
LOCKMODE lockmode)
|
|
|
|
|
{
|
|
|
|
|
Form_pg_constraint currcon;
|
|
|
|
|
bool changed = false;
|
|
|
|
|
List *otherrelids = NIL;
|
|
|
|
|
|
|
|
|
|
currcon = (Form_pg_constraint) GETSTRUCT(contuple);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Do the catalog work for the deferrability change, recurse if necessary.
|
|
|
|
|
* Do the catalog work for the enforceability or deferrability change,
|
|
|
|
|
* recurse if necessary.
|
|
|
|
|
*
|
|
|
|
|
* Note that even if deferrability is requested to be altered along with
|
|
|
|
|
* enforceability, we don't need to explicitly update multiple entries in
|
|
|
|
|
* pg_trigger related to deferrability.
|
|
|
|
|
*
|
|
|
|
|
* Modifying enforceability involves either creating or dropping the
|
|
|
|
|
* trigger, during which the deferrability setting will be adjusted
|
|
|
|
|
* automatically.
|
|
|
|
|
*/
|
|
|
|
|
if (cmdcon->alterDeferrability &&
|
|
|
|
|
ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
|
|
|
|
|
contuple, recurse, &otherrelids,
|
|
|
|
|
lockmode))
|
|
|
|
|
if (cmdcon->alterEnforceability &&
|
|
|
|
|
ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
|
|
|
|
|
currcon->conrelid, currcon->confrelid,
|
|
|
|
|
contuple, lockmode, InvalidOid,
|
|
|
|
|
InvalidOid, InvalidOid, InvalidOid))
|
|
|
|
|
changed = true;
|
|
|
|
|
|
|
|
|
|
else if (cmdcon->alterDeferrability &&
|
|
|
|
|
ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
|
|
|
|
|
contuple, recurse, &otherrelids,
|
|
|
|
|
lockmode))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* AlterConstrUpdateConstraintEntry already invalidated relcache for
|
|
|
|
|
@@ -12149,6 +12243,151 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Returns true if the constraint's enforceability is altered.
|
|
|
|
|
*
|
|
|
|
|
* Depending on whether the constraint is being set to ENFORCED or NOT
|
|
|
|
|
* ENFORCED, it creates or drops the trigger accordingly.
|
|
|
|
|
*
|
|
|
|
|
* Note that we must recurse even when trying to change a constraint to not
|
|
|
|
|
* enforced if it is already not enforced, in case descendant constraints
|
|
|
|
|
* might be enforced and need to be changed to not enforced. Conversely, we
|
|
|
|
|
* should do nothing if a constraint is being set to enforced and is already
|
|
|
|
|
* enforced, as descendant constraints cannot be different in that case.
|
|
|
|
|
*/
|
|
|
|
|
static bool
|
|
|
|
|
ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
Relation conrel, Relation tgrel,
|
|
|
|
|
const Oid fkrelid, const Oid pkrelid,
|
|
|
|
|
HeapTuple contuple, LOCKMODE lockmode,
|
|
|
|
|
Oid ReferencedParentDelTrigger,
|
|
|
|
|
Oid ReferencedParentUpdTrigger,
|
|
|
|
|
Oid ReferencingParentInsTrigger,
|
|
|
|
|
Oid ReferencingParentUpdTrigger)
|
|
|
|
|
{
|
|
|
|
|
Form_pg_constraint currcon;
|
|
|
|
|
Oid conoid;
|
|
|
|
|
Relation rel;
|
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
|
|
/* Since this function recurses, it could be driven to stack overflow */
|
|
|
|
|
check_stack_depth();
|
|
|
|
|
|
|
|
|
|
Assert(cmdcon->alterEnforceability);
|
|
|
|
|
|
|
|
|
|
currcon = (Form_pg_constraint) GETSTRUCT(contuple);
|
|
|
|
|
conoid = currcon->oid;
|
|
|
|
|
|
|
|
|
|
/* Should be foreign key constraint */
|
|
|
|
|
Assert(currcon->contype == CONSTRAINT_FOREIGN);
|
|
|
|
|
|
|
|
|
|
rel = table_open(currcon->conrelid, lockmode);
|
|
|
|
|
|
|
|
|
|
if (currcon->conenforced != cmdcon->is_enforced)
|
|
|
|
|
{
|
|
|
|
|
AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Drop triggers */
|
|
|
|
|
if (!cmdcon->is_enforced)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* When setting a constraint to NOT ENFORCED, the constraint triggers
|
|
|
|
|
* need to be dropped. Therefore, we must process the child relations
|
|
|
|
|
* first, followed by the parent, to account for dependencies.
|
|
|
|
|
*/
|
|
|
|
|
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
|
|
|
|
|
get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
|
|
|
|
|
AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
|
|
|
|
|
fkrelid, pkrelid, contuple,
|
|
|
|
|
lockmode, InvalidOid, InvalidOid,
|
|
|
|
|
InvalidOid, InvalidOid);
|
|
|
|
|
|
|
|
|
|
/* Drop all the triggers */
|
|
|
|
|
DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
|
|
|
|
|
}
|
|
|
|
|
else if (changed) /* Create triggers */
|
|
|
|
|
{
|
|
|
|
|
Oid ReferencedDelTriggerOid = InvalidOid,
|
|
|
|
|
ReferencedUpdTriggerOid = InvalidOid,
|
|
|
|
|
ReferencingInsTriggerOid = InvalidOid,
|
|
|
|
|
ReferencingUpdTriggerOid = InvalidOid;
|
|
|
|
|
|
|
|
|
|
/* Prepare the minimal information required for trigger creation. */
|
|
|
|
|
Constraint *fkconstraint = makeNode(Constraint);
|
|
|
|
|
|
|
|
|
|
fkconstraint->conname = pstrdup(NameStr(currcon->conname));
|
|
|
|
|
fkconstraint->fk_matchtype = currcon->confmatchtype;
|
|
|
|
|
fkconstraint->fk_upd_action = currcon->confupdtype;
|
|
|
|
|
fkconstraint->fk_del_action = currcon->confdeltype;
|
|
|
|
|
|
|
|
|
|
/* Create referenced triggers */
|
|
|
|
|
if (currcon->conrelid == fkrelid)
|
|
|
|
|
createForeignKeyActionTriggers(currcon->conrelid,
|
|
|
|
|
currcon->confrelid,
|
|
|
|
|
fkconstraint,
|
|
|
|
|
conoid,
|
|
|
|
|
currcon->conindid,
|
|
|
|
|
ReferencedParentDelTrigger,
|
|
|
|
|
ReferencedParentUpdTrigger,
|
|
|
|
|
&ReferencedDelTriggerOid,
|
|
|
|
|
&ReferencedUpdTriggerOid);
|
|
|
|
|
|
|
|
|
|
/* Create referencing triggers */
|
|
|
|
|
if (currcon->confrelid == pkrelid)
|
|
|
|
|
createForeignKeyCheckTriggers(currcon->conrelid,
|
|
|
|
|
pkrelid,
|
|
|
|
|
fkconstraint,
|
|
|
|
|
conoid,
|
|
|
|
|
currcon->conindid,
|
|
|
|
|
ReferencingParentInsTrigger,
|
|
|
|
|
ReferencingParentUpdTrigger,
|
|
|
|
|
&ReferencingInsTriggerOid,
|
|
|
|
|
&ReferencingUpdTriggerOid);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Tell Phase 3 to check that the constraint is satisfied by existing
|
|
|
|
|
* rows.
|
|
|
|
|
*/
|
|
|
|
|
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
|
|
|
|
{
|
|
|
|
|
AlteredTableInfo *tab;
|
|
|
|
|
NewConstraint *newcon;
|
|
|
|
|
|
|
|
|
|
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
|
|
|
|
|
newcon->name = fkconstraint->conname;
|
|
|
|
|
newcon->contype = CONSTR_FOREIGN;
|
|
|
|
|
newcon->refrelid = currcon->confrelid;
|
|
|
|
|
newcon->refindid = currcon->conindid;
|
|
|
|
|
newcon->conid = currcon->oid;
|
|
|
|
|
newcon->qual = (Node *) fkconstraint;
|
|
|
|
|
|
|
|
|
|
/* Find or create work queue entry for this table */
|
|
|
|
|
tab = ATGetQueueEntry(wqueue, rel);
|
|
|
|
|
tab->constraints = lappend(tab->constraints, newcon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the table at either end of the constraint is partitioned, we
|
|
|
|
|
* need to recurse and create triggers for each constraint that is a
|
|
|
|
|
* child of this one.
|
|
|
|
|
*/
|
|
|
|
|
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
|
|
|
|
|
get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
|
|
|
|
|
AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
|
|
|
|
|
fkrelid, pkrelid, contuple,
|
|
|
|
|
lockmode, ReferencedDelTriggerOid,
|
|
|
|
|
ReferencedUpdTriggerOid,
|
|
|
|
|
ReferencingInsTriggerOid,
|
|
|
|
|
ReferencingUpdTriggerOid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
table_close(rel, NoLock);
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Returns true if the constraint's deferrability is altered.
|
|
|
|
|
*
|
|
|
|
|
@@ -12353,6 +12592,55 @@ AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
|
|
|
|
|
systable_endscan(tgscan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Invokes ATExecAlterConstrEnforceability for each constraint that is a child of
|
|
|
|
|
* the specified constraint.
|
|
|
|
|
*
|
|
|
|
|
* Note that this doesn't handle recursion the normal way, viz. by scanning the
|
|
|
|
|
* list of child relations and recursing; instead it uses the conparentid
|
|
|
|
|
* relationships. This may need to be reconsidered.
|
|
|
|
|
*
|
|
|
|
|
* The arguments to this function have the same meaning as the arguments to
|
|
|
|
|
* ATExecAlterConstrEnforceability.
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
|
|
|
|
|
Relation conrel, Relation tgrel,
|
|
|
|
|
const Oid fkrelid, const Oid pkrelid,
|
|
|
|
|
HeapTuple contuple, LOCKMODE lockmode,
|
|
|
|
|
Oid ReferencedParentDelTrigger,
|
|
|
|
|
Oid ReferencedParentUpdTrigger,
|
|
|
|
|
Oid ReferencingParentInsTrigger,
|
|
|
|
|
Oid ReferencingParentUpdTrigger)
|
|
|
|
|
{
|
|
|
|
|
Form_pg_constraint currcon;
|
|
|
|
|
Oid conoid;
|
|
|
|
|
ScanKeyData pkey;
|
|
|
|
|
SysScanDesc pscan;
|
|
|
|
|
HeapTuple childtup;
|
|
|
|
|
|
|
|
|
|
currcon = (Form_pg_constraint) GETSTRUCT(contuple);
|
|
|
|
|
conoid = currcon->oid;
|
|
|
|
|
|
|
|
|
|
ScanKeyInit(&pkey,
|
|
|
|
|
Anum_pg_constraint_conparentid,
|
|
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
|
ObjectIdGetDatum(conoid));
|
|
|
|
|
|
|
|
|
|
pscan = systable_beginscan(conrel, ConstraintParentIndexId,
|
|
|
|
|
true, NULL, 1, &pkey);
|
|
|
|
|
|
|
|
|
|
while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
|
|
|
|
|
ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
|
|
|
|
|
pkrelid, childtup, lockmode,
|
|
|
|
|
ReferencedParentDelTrigger,
|
|
|
|
|
ReferencedParentUpdTrigger,
|
|
|
|
|
ReferencingParentInsTrigger,
|
|
|
|
|
ReferencingParentUpdTrigger);
|
|
|
|
|
|
|
|
|
|
systable_endscan(pscan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Invokes ATExecAlterConstrDeferrability for each constraint that is a child of
|
|
|
|
|
* the specified constraint.
|
|
|
|
|
@@ -12413,11 +12701,25 @@ AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
|
|
|
|
|
HeapTuple copyTuple;
|
|
|
|
|
Form_pg_constraint copy_con;
|
|
|
|
|
|
|
|
|
|
Assert(cmdcon->alterDeferrability || cmdcon->alterInheritability);
|
|
|
|
|
Assert(cmdcon->alterEnforceability || cmdcon->alterDeferrability ||
|
|
|
|
|
cmdcon->alterInheritability);
|
|
|
|
|
|
|
|
|
|
copyTuple = heap_copytuple(contuple);
|
|
|
|
|
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
|
|
|
|
|
|
|
|
|
|
if (cmdcon->alterEnforceability)
|
|
|
|
|
{
|
|
|
|
|
copy_con->conenforced = cmdcon->is_enforced;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* NB: The convalidated status is irrelevant when the constraint is
|
|
|
|
|
* set to NOT ENFORCED, but for consistency, it should still be set
|
|
|
|
|
* appropriately. Similarly, if the constraint is later changed to
|
|
|
|
|
* ENFORCED, validation will be performed during phase 3, so it makes
|
|
|
|
|
* sense to mark it as valid in that case.
|
|
|
|
|
*/
|
|
|
|
|
copy_con->convalidated = cmdcon->is_enforced;
|
|
|
|
|
}
|
|
|
|
|
if (cmdcon->alterDeferrability)
|
|
|
|
|
{
|
|
|
|
|
copy_con->condeferrable = cmdcon->deferrable;
|
|
|
|
|
@@ -17137,9 +17439,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
|
|
|
|
|
NameStr(child_con->conname), RelationGetRelationName(child_rel))));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* A non-enforced child constraint cannot be merged with an
|
|
|
|
|
* enforced parent constraint. However, the reverse is allowed,
|
|
|
|
|
* where the child constraint is enforced.
|
|
|
|
|
* A NOT ENFORCED child constraint cannot be merged with an
|
|
|
|
|
* ENFORCED parent constraint. However, the reverse is allowed,
|
|
|
|
|
* where the child constraint is ENFORCED.
|
|
|
|
|
*/
|
|
|
|
|
if (parent_con->conenforced && !child_con->conenforced)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
@@ -20510,8 +20812,6 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|
|
|
|
ForeignKeyCacheInfo *fk = lfirst(cell);
|
|
|
|
|
HeapTuple contup;
|
|
|
|
|
Form_pg_constraint conform;
|
|
|
|
|
Oid insertTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
|
|
|
|
|
contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
|
|
|
|
|
if (!HeapTupleIsValid(contup))
|
|
|
|
|
@@ -20538,17 +20838,25 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Also, look up the partition's "check" triggers corresponding to the
|
|
|
|
|
* constraint being detached and detach them from the parent triggers.
|
|
|
|
|
* ENFORCED constraint being detached and detach them from the parent
|
|
|
|
|
* triggers. NOT ENFORCED constraints do not have these triggers;
|
|
|
|
|
* therefore, this step is not needed.
|
|
|
|
|
*/
|
|
|
|
|
GetForeignKeyCheckTriggers(trigrel,
|
|
|
|
|
fk->conoid, fk->confrelid, fk->conrelid,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
Assert(OidIsValid(insertTriggerOid));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
|
|
|
|
|
RelationGetRelid(partRel));
|
|
|
|
|
Assert(OidIsValid(updateTriggerOid));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
|
|
|
|
|
RelationGetRelid(partRel));
|
|
|
|
|
if (fk->conenforced)
|
|
|
|
|
{
|
|
|
|
|
Oid insertTriggerOid,
|
|
|
|
|
updateTriggerOid;
|
|
|
|
|
|
|
|
|
|
GetForeignKeyCheckTriggers(trigrel,
|
|
|
|
|
fk->conoid, fk->confrelid, fk->conrelid,
|
|
|
|
|
&insertTriggerOid, &updateTriggerOid);
|
|
|
|
|
Assert(OidIsValid(insertTriggerOid));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
|
|
|
|
|
RelationGetRelid(partRel));
|
|
|
|
|
Assert(OidIsValid(updateTriggerOid));
|
|
|
|
|
TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
|
|
|
|
|
RelationGetRelid(partRel));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Lastly, create the action triggers on the referenced table, using
|
|
|
|
|
@@ -20588,8 +20896,9 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
|
|
|
|
|
fkconstraint->conname = pstrdup(NameStr(conform->conname));
|
|
|
|
|
fkconstraint->deferrable = conform->condeferrable;
|
|
|
|
|
fkconstraint->initdeferred = conform->condeferred;
|
|
|
|
|
fkconstraint->is_enforced = conform->conenforced;
|
|
|
|
|
fkconstraint->skip_validation = true;
|
|
|
|
|
fkconstraint->initially_valid = true;
|
|
|
|
|
fkconstraint->initially_valid = conform->convalidated;
|
|
|
|
|
/* a few irrelevant fields omitted here */
|
|
|
|
|
fkconstraint->pktable = NULL;
|
|
|
|
|
fkconstraint->fk_attrs = NIL;
|
|
|
|
|
|