summaryrefslogtreecommitdiff
path: root/src/backend/commands
diff options
context:
space:
mode:
authorPeter Eisentraut2025-04-02 11:30:13 +0000
committerPeter Eisentraut2025-04-02 11:36:44 +0000
commiteec0040c4bcd650993bb058ebdf61ab94171fda4 (patch)
tree5b38744ff96722b149336728c50f0d973dc90bc8 /src/backend/commands
parent327d987df1e72a9b146f312df0a5ed34ef148720 (diff)
Add support for NOT ENFORCED in foreign key constraints
This expands the NOT ENFORCED constraint flag, previously only supported for CHECK constraints (commit ca87c415e2f), to foreign key constraints. Normally, when a foreign key constraint is created on a table, action and check triggers are added to maintain data integrity. With this patch, if a constraint is marked as NOT ENFORCED, integrity checks are no longer required, making these triggers unnecessary. Consequently, when creating a NOT ENFORCED foreign key constraint, triggers will not be created, and the constraint will be marked as NOT VALID. Similarly, if an existing foreign key constraint is changed to NOT ENFORCED, the associated triggers will be dropped, and the constraint will also be marked as NOT VALID. Conversely, if a NOT ENFORCED foreign key constraint is changed to ENFORCED, the necessary triggers will be created, and the will be changed to VALID by performing necessary validation. Since not-enforced foreign key constraints have no triggers, the shortcut used for example in psql and pg_dump to skip looking for foreign keys if the relation is known not to have triggers no longer applies. (It already didn't work for partitioned tables.) Author: Amul Sul <[email protected]> Reviewed-by: Joel Jacobson <[email protected]> Reviewed-by: Andrew Dunstan <[email protected]> Reviewed-by: Peter Eisentraut <[email protected]> Reviewed-by: jian he <[email protected]> Reviewed-by: Alvaro Herrera <[email protected]> Reviewed-by: Ashutosh Bapat <[email protected]> Reviewed-by: Isaac Morland <[email protected]> Reviewed-by: Alexandra Wang <[email protected]> Tested-by: Triveni N <[email protected]> Discussion: https://www.postgresql.org/message-id/flat/CAAJ_b962c5AcYW9KUt_R_ER5qs3fUGbe4az-SP-vuwPS-w-AGA@mail.gmail.com
Diffstat (limited to 'src/backend/commands')
-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;