aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/tablecmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r--src/backend/commands/tablecmds.c473
1 files changed, 391 insertions, 82 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 10624353b0a..f47b82dbcf3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -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.
- */
- if (cmdcon->alterDeferrability &&
- ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
- contuple, recurse, &otherrelids,
- lockmode))
+ * 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->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
@@ -12150,6 +12244,151 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
}
/*
+ * 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.
*
* *otherrelids is appended OIDs of relations containing affected triggers.
@@ -12354,6 +12593,55 @@ AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
}
/*
+ * 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;