@@ -404,6 +404,9 @@ static bool ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cm
404404 Oid ReferencedParentUpdTrigger,
405405 Oid ReferencingParentInsTrigger,
406406 Oid ReferencingParentUpdTrigger);
407+ static bool ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
408+ Relation conrel, HeapTuple contuple,
409+ bool recurse, bool recursing, LOCKMODE lockmode);
407410static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
408411 Relation conrel, Relation tgrel, Relation rel,
409412 HeapTuple contuple, bool recurse,
@@ -422,6 +425,9 @@ static void AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *c
422425 Oid ReferencedParentUpdTrigger,
423426 Oid ReferencingParentInsTrigger,
424427 Oid ReferencingParentUpdTrigger);
428+ static void AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
429+ Relation conrel, Oid conrelid,
430+ bool recurse, bool recursing, LOCKMODE lockmode);
425431static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
426432 Relation conrel, Relation tgrel, Relation rel,
427433 HeapTuple contuple, bool recurse,
@@ -12181,7 +12187,7 @@ GetForeignKeyCheckTriggers(Relation trigrel,
1218112187 *
1218212188 * Update the attributes of a constraint.
1218312189 *
12184- * Currently only works for Foreign Key and not null constraints.
12190+ * Currently works for Foreign Key, CHECK, and not null constraints.
1218512191 *
1218612192 * If the constraint is modified, returns its address; otherwise, return
1218712193 * InvalidObjectAddress.
@@ -12243,11 +12249,13 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
1224312249 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1224412250 errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
1224512251 cmdcon->conname, RelationGetRelationName(rel))));
12246- if (cmdcon->alterEnforceability && currcon->contype != CONSTRAINT_FOREIGN)
12252+ if (cmdcon->alterEnforceability &&
12253+ (currcon->contype != CONSTRAINT_FOREIGN && currcon->contype != CONSTRAINT_CHECK))
1224712254 ereport(ERROR,
12248- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12249- errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
12250- cmdcon->conname, RelationGetRelationName(rel))));
12255+ errcode(ERRCODE_WRONG_OBJECT_TYPE),
12256+ errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
12257+ cmdcon->conname, RelationGetRelationName(rel)),
12258+ errhint("Only foreign key and check constraints can change enforceability"));
1225112259 if (cmdcon->alterInheritability &&
1225212260 currcon->contype != CONSTRAINT_NOTNULL)
1225312261 ereport(ERROR,
@@ -12349,16 +12357,20 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
1234912357 * enforceability, we don't need to explicitly update multiple entries in
1235012358 * pg_trigger related to deferrability.
1235112359 *
12352- * Modifying enforceability involves either creating or dropping the
12353- * trigger, during which the deferrability setting will be adjusted
12360+ * Modifying foreign key enforceability involves either creating or dropping
12361+ * the trigger, during which the deferrability setting will be adjusted
1235412362 * automatically.
1235512363 */
12356- if (cmdcon->alterEnforceability &&
12364+ if (cmdcon->alterEnforceability && currcon->contype == CONSTRAINT_FOREIGN &&
1235712365 ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
1235812366 currcon->conrelid, currcon->confrelid,
1235912367 contuple, lockmode, InvalidOid,
1236012368 InvalidOid, InvalidOid, InvalidOid))
1236112369 changed = true;
12370+ else if (cmdcon->alterEnforceability && currcon->contype == CONSTRAINT_CHECK &&
12371+ ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel, contuple,
12372+ recurse, false, lockmode))
12373+ changed = true;
1236212374
1236312375 else if (cmdcon->alterDeferrability &&
1236412376 ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
@@ -12389,7 +12401,140 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
1238912401}
1239012402
1239112403/*
12392- * Returns true if the constraint's enforceability is altered.
12404+ * Returns true if the CHECK constraint's enforceability is altered.
12405+ *
12406+ * Note that we must recurse even when trying to change a check constraint to
12407+ * not enforced if it is already not enforced, in case descendant constraints
12408+ * might be enforced and need to be changed to not enforced. Conversely, we
12409+ * should do nothing if a constraint is being set to enforced and is already
12410+ * enforced, as descendant constraints cannot be different in that case.
12411+ */
12412+ static bool
12413+ ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12414+ Relation conrel, HeapTuple contuple,
12415+ bool recurse, bool recursing, LOCKMODE lockmode)
12416+ {
12417+ Form_pg_constraint currcon;
12418+ Relation rel;
12419+ bool changed = false;
12420+ List *children = NIL;
12421+
12422+ /* Since this function recurses, it could be driven to stack overflow */
12423+ check_stack_depth();
12424+
12425+ Assert(cmdcon->alterEnforceability);
12426+
12427+ currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12428+
12429+ Assert(currcon->contype == CONSTRAINT_CHECK);
12430+
12431+ rel = table_open(currcon->conrelid, lockmode);
12432+ if (currcon->conenforced != cmdcon->is_enforced)
12433+ {
12434+ AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12435+ changed = true;
12436+ }
12437+
12438+ if (!cmdcon->is_enforced || changed)
12439+ {
12440+ /*
12441+ * If we're recursing, the parent has already done this, so skip it.
12442+ * Also, if the constraint is a NO INHERIT constraint, we shouldn't try
12443+ * to look for it in the children.
12444+ */
12445+ if (!recursing && !currcon->connoinherit)
12446+ children = find_all_inheritors(RelationGetRelid(rel),
12447+ lockmode, NULL);
12448+
12449+ foreach_oid(childoid, children)
12450+ {
12451+ if (childoid == RelationGetRelid(rel))
12452+ continue;
12453+
12454+ /*
12455+ * If we are told not to recurse, there had better not be any child
12456+ * tables, because we can't change constraint enforceability on the
12457+ * parent unless we have changed enforceability for all child.
12458+ */
12459+ if (!recurse)
12460+ ereport(ERROR,
12461+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12462+ errmsg("constraint must be altered on child tables too"));
12463+
12464+ AlterCheckConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, childoid, false, true, lockmode);
12465+ }
12466+ }
12467+
12468+ /*
12469+ * Tell Phase 3 to check that the constraint is satisfied by existing rows.
12470+ * We do this only when alter the constraint from not enforced to enforced.
12471+ */
12472+ if (rel->rd_rel->relkind == RELKIND_RELATION &&
12473+ cmdcon->is_enforced &&
12474+ !currcon->conenforced)
12475+ {
12476+ AlteredTableInfo *tab;
12477+ NewConstraint *newcon;
12478+ Datum val;
12479+ char *conbin;
12480+
12481+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
12482+ newcon->name = pstrdup(NameStr(currcon->conname));
12483+ newcon->contype = CONSTR_CHECK;
12484+ val = SysCacheGetAttrNotNull(CONSTROID, contuple,
12485+ Anum_pg_constraint_conbin);
12486+ conbin = TextDatumGetCString(val);
12487+ newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
12488+
12489+ /* Find or create work queue entry for this table */
12490+ tab = ATGetQueueEntry(wqueue, rel);
12491+ tab->constraints = lappend(tab->constraints, newcon);
12492+ }
12493+
12494+ table_close(rel, NoLock);
12495+ return changed;
12496+ }
12497+
12498+ static void
12499+ AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
12500+ Relation conrel, Oid conrelid,
12501+ bool recurse, bool recursing,
12502+ LOCKMODE lockmode)
12503+ {
12504+ SysScanDesc pscan;
12505+ HeapTuple childtup;
12506+ ScanKeyData skey[3];
12507+
12508+ ScanKeyInit(&skey[0],
12509+ Anum_pg_constraint_conrelid,
12510+ BTEqualStrategyNumber, F_OIDEQ,
12511+ ObjectIdGetDatum(conrelid));
12512+ ScanKeyInit(&skey[1],
12513+ Anum_pg_constraint_contypid,
12514+ BTEqualStrategyNumber, F_OIDEQ,
12515+ ObjectIdGetDatum(InvalidOid));
12516+ ScanKeyInit(&skey[2],
12517+ Anum_pg_constraint_conname,
12518+ BTEqualStrategyNumber, F_NAMEEQ,
12519+ CStringGetDatum(cmdcon->conname));
12520+
12521+ pscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
12522+ NULL, 3, skey);
12523+
12524+ if (!HeapTupleIsValid(childtup = systable_getnext(pscan)))
12525+ ereport(ERROR,
12526+ errcode(ERRCODE_UNDEFINED_OBJECT),
12527+ errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12528+ cmdcon->conname, get_rel_name(conrelid)));
12529+
12530+ ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel, childtup,
12531+ recurse, recursing, lockmode);
12532+
12533+ systable_endscan(pscan);
12534+ }
12535+
12536+ /*
12537+ * Returns true if the FOREIGN KEY constraint's enforceability is altered.
1239312538 *
1239412539 * Depending on whether the constraint is being set to ENFORCED or NOT
1239512540 * ENFORCED, it creates or drops the trigger accordingly.
0 commit comments