Re: NOT NULL NOT ENFORCED

From: Álvaro Herrera <alvherre(at)kurilemu(dot)de>
To: jian he <jian(dot)universality(at)gmail(dot)com>
Cc: PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: NOT NULL NOT ENFORCED
Date: 2025-09-04 12:00:29
Message-ID: 202509041200.iivbiv66fp62@alvherre.pgsql
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On 2025-Sep-04, jian he wrote:

> @@ -3093,6 +3115,16 @@ AddRelationNotNullConstraints(Relation rel, List *constraints,
> conname = other->name;
>
> inhcount++;
> +
> + /*
> + * if a column inherit multiple not-null constraints, the
> + * enforced status should the same.
> + */
> + if (other->is_enforced != cooked->is_enforced)
> + ereport(ERROR,
> + errcode(ERRCODE_DATATYPE_MISMATCH),
> + errmsg("cannot define not-null constraint on column \"%s\"", conname),
> + errdetail("The column inherited not-null constraints have conflict ENFORCED status."));
> old_notnulls = list_delete_nth_cell(old_notnulls, restpos);
> }
> else

Hmmm, are you sure about this? I think if a table has two parents, one
with enforced and the other with not enforced constraint, then it's okay
to get them combined resulting in one enforced constraint.

> @@ -777,6 +778,18 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum,
> errhint("You might need to validate it using %s.",
> "ALTER TABLE ... VALIDATE CONSTRAINT"));
>
> + /*
> + * If the ENFORCED status we're asked for doesn't match what the
> + * existing constraint has, throw an error.
> + */
> + if (is_enforced != conform->conenforced)
> + ereport(ERROR,
> + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> + errmsg("cannot change ENFORCED status of NOT NULL constraint \"%s\" on relation \"%s\"",
> + NameStr(conform->conname), get_rel_name(relid)),
> + errhint("You might need to drop the existing not enforced constraint using %s.",
> + "ALTER TABLE ... DROP CONSTRAINT"));

I think the hint here should suggest to make the existing constraint as
enforced, rather than drop it.

> @@ -9937,9 +9962,9 @@ ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
> * If adding a valid not-null constraint, set the pg_attribute flag
> * and tell phase 3 to verify existing rows, if needed. For an
> * invalid constraint, just set attnotnull, without queueing
> - * verification.
> + * verification. For not enforced not-null, no need set attnotnull.
> */
> - if (constr->contype == CONSTR_NOTNULL)
> + if (constr->contype == CONSTR_NOTNULL && ccon->is_enforced)
> set_attnotnull(wqueue, rel, ccon->attnum,
> !constr->skip_validation,
> !constr->skip_validation);

Didn't we decide that attnotnull meant whether a constraint exists at
all, without making a judgement on whether it's enforced or valid? The
important change should be in CheckNNConstraintFetch() which should
determine attnullability in a way that allows executor know whether the
column is nullable or not. I admit we might want to handle this
differently for unenforced constraints, but we should discuss that and
make sure that's what we want.

> + else if (notenforced)
> + {
> + /*
> + * We can't use ATExecSetNotNull here because it adds an enforced
> + * not-null constraint, but here we only want a non-enforced one.
> + */

Umm, wouldn't it make more sense to modify ATExecSetNotNull() so that it
does what we want? This seems hackish.

> @@ -1272,33 +1294,41 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
> * Reproduce not-null constraints, if any, by copying them. We do this
> * regardless of options given.
> */
> - if (tupleDesc->constr && tupleDesc->constr->has_not_null)
> - {
> - List *lst;
> + lst = RelationGetNotNullConstraints(RelationGetRelid(relation), false,
> + true);
> + cxt->nnconstraints = list_concat(cxt->nnconstraints, lst);
>
> - lst = RelationGetNotNullConstraints(RelationGetRelid(relation), false,
> - true);

> + /*
> + * When creating a new relation, marking the enforced not-null constraint as
> + * not valid doesn't make sense, so we treat it as valid.
> + */
> + foreach_node(Constraint, nnconstr, lst)
> + {
> + if (nnconstr->is_enforced)
> + {
> + nnconstr->skip_validation = false;
> + nnconstr->initially_valid = true;
> + }
> + }

Hmmm, this bit here (making constraints as valid if they're not valid in
the source table) looks like a fix for the existing code. I think it
should be a separate patch, perhaps back-patchable to 18. Or maybe I'm
missing something ...?

--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"Digital and video cameras have this adjustment and film cameras don't for the
same reason dogs and cats lick themselves: because they can." (Ken Rockwell)

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Dilip Kumar 2025-09-04 12:02:27 Re: Potential problem in commit f777d773878 and 4f7f7b03758
Previous Message Konstantin Knizhnik 2025-09-04 11:44:41 Re: Orphan page in _bt_split