hi. attached patch will remove sql_features.txt item F492: "Optional table constraint enforcement" Comment: "except not-null constraints". see [1]
main points about NOT NULL NOT ENFORCED * one column can have at most one NOT-NULL constraint, regardless constraints property (not enforced or enforced) * if column already have not enforced not-null constraint then: ALTER TABLE ALTER COLUMN SET NOT NULL: error out, can not validate not enforced not-null constraint ALTER TABLE ADD NOT NULL: error out, can not add another not-null constraint, one column can only have one. not null in partitioned table: * If the partitioned table has an enforced not-null constraint, its partitions cannot have not enforced. * If the partitioned table has a NOT ENFORCED not-null constraint, its partitions may have either ENFORCED or NOT ENFORCED not-null constraints, but the constraint itself is still required. not null in table inheritance: OK: parent is not enforced, while child is enforced NOT OK: parent is enforced, while child is not enforced If a column inherits from multiple tables and the ancestor tables have conflicting ENFORCED statuses, raise an error. I have written extensive tests to cover the corner case, the tests may be overwhelming. [1]: https://www.postgresql.org/docs/devel/features-sql-standard.html
From 12e06cc4c964b6c9c17c55dc94edac0a18653f2f Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Thu, 4 Sep 2025 10:52:30 +0800 Subject: [PATCH v1 1/1] NOT NULL NOT ENFORCED this will remove sql_features.txt item F492: "Optional table constraint enforcement" remarks: "except not-null constraints". See [1]. main points about NOT NULL NOT ENFORCED * one column can have at most one NOT-NULL constraint, regardless constraints property (not enforced or enforced) * If column already have not enforced not-null constraint then: ALTER TABLE ALTER COLUMN SET NOT NULL: error out, can not validate not enforced not-null constraint ALTER TABLE ADD NOT NULL: error out, can not add another not-null constraint, one column can only have one. not null in partitioned table: * If the partitioned table has an enforced not-null constraint, its partitions cannot have not enforced. * If the partitioned table has a NOT ENFORCED not-null constraint, its partitions may have either ENFORCED or NOT ENFORCED not-null constraints, but the constraint itself is still required. not null in table inheritance: OK: parent is not enforced, while child is enforced NOT OK: parent is enforced, while child is not enforced If a column inherits from multiple tables and the ancestor tables have conflicting ENFORCED statuses, raise an error. reference: https://git.postgresql.org/cgit/postgresql.git/commit/?id=a379061a22a8fdf421e1a457cc6af8503def6252 discussion: https://postgr.es/m/ [1]: https://www.postgresql.org/docs/devel/features-sql-standard.html --- doc/src/sgml/ref/alter_table.sgml | 4 +- doc/src/sgml/ref/create_table.sgml | 4 +- src/backend/catalog/heap.c | 61 +++++-- src/backend/catalog/pg_constraint.c | 38 ++-- src/backend/catalog/sql_features.txt | 2 +- src/backend/commands/tablecmds.c | 129 +++++++++++--- src/backend/parser/gram.y | 2 +- src/backend/parser/parse_utilcmd.c | 80 ++++++--- src/backend/utils/cache/relcache.c | 2 +- src/bin/psql/describe.c | 20 ++- src/include/catalog/heap.h | 2 +- src/include/catalog/pg_constraint.h | 4 +- src/test/regress/expected/constraints.out | 162 ++++++++++++++++++ .../regress/expected/create_table_like.out | 22 +++ src/test/regress/expected/inherit.out | 107 ++++++++++++ src/test/regress/sql/constraints.sql | 88 ++++++++++ src/test/regress/sql/create_table_like.sql | 12 ++ src/test/regress/sql/inherit.sql | 62 +++++++ 18 files changed, 715 insertions(+), 86 deletions(-) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 8867da6c693..e8a93c97432 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1481,8 +1481,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM Adding a <literal>CHECK</literal> or <literal>NOT NULL</literal> constraint requires scanning the table to verify that existing rows meet the constraint, but does not require a table rewrite. If a <literal>CHECK</literal> - constraint is added as <literal>NOT ENFORCED</literal>, no verification will - be performed. + or <literal>NOT NULL</literal> constraint is added as <literal>NOT ENFORCED</literal>, + no verification will be performed. </para> <para> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index dc000e913c1..4d0d0e9cbf4 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1425,8 +1425,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM </para> <para> - This is currently only supported for foreign key and <literal>CHECK</literal> - constraints. + This is currently only supported for foreign key, <literal>CHECK</literal> + and <literal>NOT NULL</literal> constraints. </para> </listitem> </varlistentry> diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index fd6537567ea..2a8f4c42bac 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2252,8 +2252,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr, */ static Oid StoreRelNotNull(Relation rel, const char *nnname, AttrNumber attnum, - bool is_validated, bool is_local, int inhcount, - bool is_no_inherit) + bool is_enforced, bool is_validated, bool is_local, + int inhcount, bool is_no_inherit) { Oid constrOid; @@ -2265,7 +2265,7 @@ StoreRelNotNull(Relation rel, const char *nnname, AttrNumber attnum, CONSTRAINT_NOTNULL, false, false, - true, /* Is Enforced */ + is_enforced, /* Is Enforced */ is_validated, InvalidOid, RelationGetRelid(rel), @@ -2627,16 +2627,18 @@ AddRelationNewConstraints(Relation rel, strVal(linitial(cdef->keys)))); Assert(cdef->initially_valid != cdef->skip_validation); + Assert(cdef->is_enforced || !cdef->initially_valid); /* * If the column already has a not-null constraint, we don't want * to add another one; adjust inheritance status as needed. This * also checks whether the existing constraint matches the - * requested validity. + * requested validity or enforceability. */ if (AdjustNotNullInheritance(RelationGetRelid(rel), colnum, is_local, cdef->is_no_inherit, - cdef->skip_validation)) + cdef->skip_validation, + cdef->is_enforced)) continue; /* @@ -2664,6 +2666,7 @@ AddRelationNewConstraints(Relation rel, constrOid = StoreRelNotNull(rel, nnname, colnum, + cdef->is_enforced, cdef->initially_valid, is_local, inhcount, @@ -2675,7 +2678,7 @@ AddRelationNewConstraints(Relation rel, nncooked->name = nnname; nncooked->attnum = colnum; nncooked->expr = NULL; - nncooked->is_enforced = true; + nncooked->is_enforced = cdef->is_enforced; nncooked->skip_validation = cdef->skip_validation; nncooked->is_local = is_local; nncooked->inhcount = inhcount; @@ -2949,7 +2952,7 @@ AddRelationNotNullConstraints(Relation rel, List *constraints, /* * A column can only have one not-null constraint, so discard any * additional ones that appear for columns we already saw; but check - * that the NO INHERIT flags match. + * that the NO INHERIT, NOT ENFORCED flags match. */ for (int restpos = outerpos + 1; restpos < list_length(constraints);) { @@ -2965,6 +2968,11 @@ AddRelationNotNullConstraints(Relation rel, List *constraints, errmsg("conflicting NO INHERIT declaration for not-null constraint on column \"%s\"", strVal(linitial(constr->keys)))); + if (other->is_enforced != constr->is_enforced) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting NOT ENFORCED declaration for not-null constraint on column \"%s\"", + strVal(linitial(constr->keys)))); /* * Preserve constraint name if one is specified, but raise an * error if conflicting ones are specified. @@ -3010,6 +3018,17 @@ AddRelationNotNullConstraints(Relation rel, List *constraints, strVal(linitial(constr->keys))), errdetail("The column has an inherited not-null constraint."))); + /* + * If we get a ENFORCED constraint from the parent, having a + * local NOT ENFORCED one doesn't work. + */ + if (old->is_enforced && !constr->is_enforced) + ereport(ERROR, + errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot define not-null constraint with NOT ENFORCED on column \"%s\"", + strVal(linitial(constr->keys))), + errdetail("The column has an inherited ENFORCED not-null constraint.")); + inhcount++; old_notnulls = foreach_delete_current(old_notnulls, old); } @@ -3044,11 +3063,14 @@ AddRelationNotNullConstraints(Relation rel, List *constraints, nnnames); nnnames = lappend(nnnames, conname); - StoreRelNotNull(rel, conname, - attnum, true, true, - inhcount, constr->is_no_inherit); + Assert(constr->is_enforced || constr->skip_validation); + StoreRelNotNull(rel, conname, attnum, + constr->is_enforced, + !constr->skip_validation, + true, inhcount, constr->is_no_inherit); - nncols = lappend_int(nncols, attnum); + if (constr->is_enforced) + nncols = lappend_int(nncols, attnum); } /* @@ -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 @@ -3123,10 +3155,13 @@ AddRelationNotNullConstraints(Relation rel, List *constraints, nnnames = lappend(nnnames, conname); /* ignore the origin constraint's is_local and inhcount */ - StoreRelNotNull(rel, conname, cooked->attnum, true, + StoreRelNotNull(rel, conname, cooked->attnum, + cooked->is_enforced, + cooked->is_enforced ? true : false, false, inhcount, false); - nncols = lappend_int(nncols, cooked->attnum); + if (cooked->is_enforced) + nncols = lappend_int(nncols, cooked->attnum); } return nncols; diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 6002fd0002f..a625813cd6f 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -100,9 +100,10 @@ CreateConstraintEntry(const char *constraintName, ObjectAddresses *addrs_auto; ObjectAddresses *addrs_normal; - /* Only CHECK or FOREIGN KEY constraint can be not enforced */ - Assert(isEnforced || constraintType == CONSTRAINT_CHECK || - constraintType == CONSTRAINT_FOREIGN); + /* Only CHECK, FOREIGN KEY, NOT NULL constraint can be not enforced */ + Assert(isEnforced || (constraintType == CONSTRAINT_CHECK || + constraintType == CONSTRAINT_FOREIGN || + constraintType == CONSTRAINT_NOTNULL)); /* NOT ENFORCED constraint must be NOT VALID */ Assert(isEnforced || !isValidated); @@ -580,8 +581,8 @@ ChooseConstraintName(const char *name1, const char *name2, } /* - * Find and return a copy of the pg_constraint tuple that implements a - * (possibly not valid) not-null constraint for the given column of the + * Find and return a copy of the pg_constraint tuple that implements a (possibly + * not valid or not enforced) not-null constraint for the given column of the * given relation. If no such constraint exists, return NULL. * * XXX This would be easier if we had pg_attribute.notnullconstr with the OID @@ -634,8 +635,8 @@ findNotNullConstraintAttnum(Oid relid, AttrNumber attnum) /* * Find and return a copy of the pg_constraint tuple that implements a - * (possibly not valid) not-null constraint for the given column of the - * given relation. + * (possibly not valid or not enforced) not-null constraint for the given column + * of the given relation. * If no such column or no such constraint exists, return NULL. */ HeapTuple @@ -738,8 +739,8 @@ extractNotNullColumn(HeapTuple constrTup) * it's already true; otherwise we increment coninhcount by 1. */ bool -AdjustNotNullInheritance(Oid relid, AttrNumber attnum, - bool is_local, bool is_no_inherit, bool is_notvalid) +AdjustNotNullInheritance(Oid relid, AttrNumber attnum, bool is_local, + bool is_no_inherit, bool is_notvalid, bool is_enforced) { HeapTuple tup; @@ -769,7 +770,7 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum, * Throw an error if the existing constraint is NOT VALID and caller * wants a valid one. */ - if (!is_notvalid && !conform->convalidated) + if (!is_notvalid && !conform->convalidated && conform->conenforced) ereport(ERROR, errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"", @@ -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")); + if (!is_local) { if (pg_add_s16_overflow(conform->coninhcount, 1, @@ -807,6 +820,7 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum, * RelationGetNotNullConstraints * Return the list of not-null constraints for the given rel * + * The returned not-null constraints possibly not enforced! * Caller can request cooked constraints, or raw. * * This is seldom needed, so we just scan pg_constraint each time. @@ -853,7 +867,7 @@ RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh) cooked->name = pstrdup(NameStr(conForm->conname)); cooked->attnum = colnum; cooked->expr = NULL; - cooked->is_enforced = true; + cooked->is_enforced = conForm->conenforced; cooked->skip_validation = !conForm->convalidated; cooked->is_local = true; cooked->inhcount = 0; @@ -873,7 +887,7 @@ RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh) constr->location = -1; constr->keys = list_make1(makeString(get_attname(relid, colnum, false))); - constr->is_enforced = true; + constr->is_enforced = conForm->conenforced; constr->skip_validation = !conForm->convalidated; constr->initially_valid = true; constr->is_no_inherit = conForm->connoinherit; diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index ebe85337c28..c418e6cd1dd 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -281,7 +281,7 @@ F461 Named character sets NO F471 Scalar subquery values YES F481 Expanded NULL predicate YES F491 Constraint management YES -F492 Optional table constraint enforcement YES except not-null constraints +F492 Optional table constraint enforcement YES F501 Features and conformance views YES F501 Features and conformance views 01 SQL_FEATURES view YES F501 Features and conformance views 02 SQL_SIZING view YES diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..733fe103f7d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2740,13 +2740,16 @@ MergeAttributes(List *columns, const List *supers, char relpersistence, inherited_defaults = cols_with_defaults = NIL; /* - * Request attnotnull on columns that have a not-null constraint + * Request attnotnull on columns that have an enforced not-null constraint * that's not marked NO INHERIT (even if not valid). */ nnconstrs = RelationGetNotNullConstraints(RelationGetRelid(relation), true, false); foreach_ptr(CookedConstraint, cc, nnconstrs) - nncols = bms_add_member(nncols, cc->attnum); + { + if (cc->is_enforced) + nncols = bms_add_member(nncols, cc->attnum); + } for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++) @@ -7726,6 +7729,7 @@ add_column_collation_dependency(Oid relid, int32 attnum, Oid collid) * * Return the address of the modified column. If the column was already * nullable, InvalidObjectAddress is returned. + * This will drop the not enforced not-null constraint too. */ static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recurse, @@ -7754,13 +7758,6 @@ ATExecDropNotNull(Relation rel, const char *colName, bool recurse, ObjectAddressSubSet(address, RelationRelationId, RelationGetRelid(rel), attnum); - /* If the column is already nullable there's nothing to do. */ - if (!attTup->attnotnull) - { - table_close(attr_rel, RowExclusiveLock); - return InvalidObjectAddress; - } - /* Prevent them from altering a system attribute */ if (attnum <= 0) ereport(ERROR, @@ -7799,8 +7796,17 @@ ATExecDropNotNull(Relation rel, const char *colName, bool recurse, */ conTup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum); if (conTup == NULL) - elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"", - colName, RelationGetRelationName(rel)); + { + if (attTup->attnotnull) + elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"", + colName, RelationGetRelationName(rel)); + else + { + /* If the column is already nullable there's nothing to do. */ + table_close(attr_rel, RowExclusiveLock); + return InvalidObjectAddress; + } + } /* The normal case: we have a pg_constraint row, remove it */ dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false, @@ -7935,7 +7941,7 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName, errmsg("cannot alter system column \"%s\"", colName))); - /* See if there's already a constraint */ + /* See if there's already a constraint. Note: it maybe not enforced! */ tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum); if (HeapTupleIsValid(tuple)) { @@ -7971,7 +7977,7 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName, conForm->conislocal = true; changed = true; } - else if (!conForm->convalidated) + else if (!conForm->convalidated && conForm->conenforced) { /* * Flip attnotnull and convalidated, and also validate the @@ -7981,6 +7987,13 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName, recurse, recursing, lockmode); } + if (!conForm->conenforced) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot validate NOT ENFORCED constraint \"%s\" on relation \"%s\"", + NameStr(conForm->conname), + RelationGetRelationName(rel))); + if (changed) { Relation constr_rel; @@ -9559,7 +9572,7 @@ verifyNotNullPKCompatible(HeapTuple tuple, const char *colname) "ALTER TABLE ... ALTER CONSTRAINT ... INHERIT")); /* an unvalidated constraint is no good */ - if (!conForm->convalidated) + if (!conForm->convalidated && conForm->conenforced) ereport(ERROR, errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("cannot create primary key on column \"%s\"", colname), @@ -9569,6 +9582,18 @@ verifyNotNullPKCompatible(HeapTuple tuple, const char *colname) get_rel_name(conForm->conrelid), "NOT VALID"), errhint("You might need to validate it using %s.", "ALTER TABLE ... VALIDATE CONSTRAINT")); + + /* a not enforced constraint is no good */ + if (!conForm->conenforced) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot create primary key on column \"%s\"", colname), + /*- translator: fourth %s is a constraint characteristic such as NOT ENFORCED */ + errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.", + NameStr(conForm->conname), colname, + get_rel_name(conForm->conrelid), "NOT ENFORCED"), + errhint("You might need to drop it first using %s.", + "ALTER TABLE ... DROP CONSTRAINT")); } /* @@ -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); @@ -12584,6 +12609,7 @@ ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon, AttrNumber colNum; char *colName; List *children; + bool notenforced; Assert(cmdcon->alterInheritability); @@ -12592,6 +12618,8 @@ ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon, /* The current implementation only works for NOT NULL constraints */ Assert(currcon->contype == CONSTRAINT_NOTNULL); + notenforced = !currcon->conenforced; + /* * If called to modify a constraint that's already in the desired state, * silently do nothing. @@ -12632,6 +12660,31 @@ ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon, CatalogTupleUpdate(conrel, &childtup->t_self, childtup); heap_freetuple(childtup); } + 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. + */ + Relation childrel; + AlteredTableInfo *childtab; + Constraint *nnconstr; + + childrel = table_open(childoid, NoLock); + CheckAlterTableIsSafe(childrel); + + childtab = ATGetQueueEntry(wqueue, childrel); + + nnconstr = makeNotNullConstraint(makeString(colName)); + nnconstr->conname = pstrdup(NameStr(currcon->conname)); + nnconstr->is_enforced = false; + nnconstr->initially_valid = false; + nnconstr->skip_validation = true; + + ATAddCheckNNConstraint(wqueue, childtab, childrel, nnconstr, + true, true, false, lockmode); + table_close(childrel, NoLock); + } else { Relation childrel = table_open(childoid, NoLock); @@ -17505,9 +17558,28 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispart if (parent_att->attnotnull && !child_att->attnotnull) { HeapTuple contup; + HeapTuple childcontup; + childcontup = findNotNullConstraintAttnum(RelationGetRelid(child_rel), + child_att->attnum); contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel), parent_att->attnum); + if (HeapTupleIsValid(childcontup) && HeapTupleIsValid(contup)) + { + Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(childcontup); + Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(contup); + + /* + * If the child constraint is "not enforced" then cannot + * merge with a enforced parent constraint + */ + if (parent_con->conenforced && !child_con->conenforced) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("constraint \"%s\" conflicts with NOT ENFORCED constraint on child table \"%s\"", + NameStr(child_con->conname), RelationGetRelationName(child_rel))); + } + if (HeapTupleIsValid(contup) && !((Form_pg_constraint) GETSTRUCT(contup))->connoinherit) ereport(ERROR, @@ -17759,13 +17831,24 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) if (!found) { if (parent_con->contype == CONSTRAINT_NOTNULL) - ereport(ERROR, - errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL", - get_attname(parent_relid, - extractNotNullColumn(parent_tuple), - false), - RelationGetRelationName(child_rel))); + { + if (parent_con->conenforced) + ereport(ERROR, + errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL", + get_attname(parent_relid, + extractNotNullColumn(parent_tuple), + false), + RelationGetRelationName(child_rel))); + else + ereport(ERROR, + errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL NOT ENFORCED", + get_attname(parent_relid, + extractNotNullColumn(parent_tuple), + false), + RelationGetRelationName(child_rel))); + } ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index db43034b9db..c54131bd601 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -4258,7 +4258,7 @@ ConstraintElem: n->location = @1; n->keys = list_make1(makeString($3)); processCASbits($4, @4, "NOT NULL", - NULL, NULL, NULL, &n->skip_validation, + NULL, NULL, &n->is_enforced, &n->skip_validation, &n->is_no_inherit, yyscanner); n->initially_valid = !n->skip_validation; $$ = (Node *) n; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..b939980c068 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -317,6 +317,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) { char *colname = strVal(linitial(nn->keys)); + /* Do not set is_not_null to true for not enforced not-null constraint */ + if (!nn->is_enforced) + continue; + foreach_node(ColumnDef, cd, cxt.columns) { /* not our column? */ @@ -713,6 +717,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) * disallow it here as well. Maybe AddRelationNotNullConstraints can be * improved someday, so that it doesn't complain, and then we can remove * the restriction for SERIAL and IDENTITY here as well. + * + * Note: The above explanation apply to NOT ENFORCED not-null constraint. + * disallow_noinherit_notnull treats NOT ENFORCED the same way as NO INHERIT. */ if (!disallow_noinherit_notnull) { @@ -773,6 +780,11 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) errmsg("conflicting NO INHERIT declarations for not-null constraints on column \"%s\"", column->colname)); + if (disallow_noinherit_notnull && !constraint->is_enforced) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting NOT ENFORCED declarations for not-null constraints on column \"%s\"", + column->colname)); /* * If this is the first time we see this column being marked * not-null, add the constraint entry and keep track of it. @@ -792,6 +804,15 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) constraint->keys = list_make1(makeString(column->colname)); notnull_constraint = constraint; + + /* + * NOT ENFORCED not-null constraint does not indicate data + * are all not-null, therefore can not set column + * pg_attribute.attnotnull to true. + */ + if (!constraint->is_enforced) + column->is_not_null = false; + cxt->nnconstraints = lappend(cxt->nnconstraints, constraint); } else if (notnull_constraint) @@ -1131,6 +1152,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla AclResult aclresult; char *comment; ParseCallbackState pcbstate; + List *lst = NIL; setup_parser_errposition_callback(&pcbstate, cxt->pstate, table_like_clause->relation->location); @@ -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); - cxt->nnconstraints = list_concat(cxt->nnconstraints, lst); + /* + * 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; + } + } - /* Copy comments on not-null constraints */ - if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) + /* Copy comments on not-null constraints */ + if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS) + { + foreach_node(Constraint, nnconstr, lst) { - foreach_node(Constraint, nnconstr, lst) + if ((comment = GetComment(get_relation_constraint_oid(RelationGetRelid(relation), + nnconstr->conname, false), + ConstraintRelationId, + 0)) != NULL) { - if ((comment = GetComment(get_relation_constraint_oid(RelationGetRelid(relation), - nnconstr->conname, false), - ConstraintRelationId, - 0)) != NULL) - { - CommentStmt *stmt = makeNode(CommentStmt); + CommentStmt *stmt = makeNode(CommentStmt); - stmt->objtype = OBJECT_TABCONSTRAINT; - stmt->object = (Node *) list_make3(makeString(cxt->relation->schemaname), - makeString(cxt->relation->relname), - makeString(nnconstr->conname)); - stmt->comment = comment; - cxt->alist = lappend(cxt->alist, stmt); - } + stmt->objtype = OBJECT_TABCONSTRAINT; + stmt->object = (Node *) list_make3(makeString(cxt->relation->schemaname), + makeString(cxt->relation->relname), + makeString(nnconstr->conname)); + stmt->comment = comment; + cxt->alist = lappend(cxt->alist, stmt); } } } @@ -3992,7 +4022,8 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList) case CONSTR_ATTR_ENFORCED: if (lastprimarycon == NULL || (lastprimarycon->contype != CONSTR_CHECK && - lastprimarycon->contype != CONSTR_FOREIGN)) + lastprimarycon->contype != CONSTR_FOREIGN && + lastprimarycon->contype != CONSTR_NOTNULL )) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced ENFORCED clause"), @@ -4009,7 +4040,8 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList) case CONSTR_ATTR_NOT_ENFORCED: if (lastprimarycon == NULL || (lastprimarycon->contype != CONSTR_CHECK && - lastprimarycon->contype != CONSTR_FOREIGN)) + lastprimarycon->contype != CONSTR_FOREIGN && + lastprimarycon->contype != CONSTR_NOTNULL)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced NOT ENFORCED clause"), diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 6fe268a8eec..2333ef9b1fc 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -4632,7 +4632,7 @@ CheckNNConstraintFetch(Relation relation) */ if (conform->contype == CONSTRAINT_NOTNULL) { - if (!conform->convalidated) + if (!conform->convalidated && conform->conenforced) { AttrNumber attnum; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 4aa793d7de7..543ed309b92 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3108,7 +3108,14 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT c.conname, a.attname, c.connoinherit,\n" " c.conislocal, c.coninhcount <> 0,\n" - " c.convalidated\n" + " c.convalidated,\n"); + + if (pset.sversion >= 190000) + appendPQExpBufferStr(&buf, "c.conenforced\n"); + else + appendPQExpBufferStr(&buf, "true as conenforced\n"); + + appendPQExpBuffer(&buf, "FROM pg_catalog.pg_constraint c JOIN\n" " pg_catalog.pg_attribute a ON\n" " (a.attrelid = c.conrelid AND a.attnum = c.conkey[1])\n" @@ -3132,15 +3139,20 @@ describeOneTableDetails(const char *schemaname, bool islocal = PQgetvalue(result, i, 3)[0] == 't'; bool inherited = PQgetvalue(result, i, 4)[0] == 't'; bool validated = PQgetvalue(result, i, 5)[0] == 't'; + bool enforced = PQgetvalue(result, i, 6)[0] == 't'; - printfPQExpBuffer(&buf, " \"%s\" NOT NULL \"%s\"%s%s", + printfPQExpBuffer(&buf, " \"%s\" NOT NULL \"%s\"%s", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1), PQgetvalue(result, i, 2)[0] == 't' ? " NO INHERIT" : islocal && inherited ? _(" (local, inherited)") : - inherited ? _(" (inherited)") : "", - !validated ? " NOT VALID" : ""); + inherited ? _(" (inherited)") : ""); + + if (!enforced) + appendPQExpBufferStr(&buf, " NOT ENFORCED"); + else if (!validated) + appendPQExpBufferStr(&buf, " NOT VALID"); printTableAddFooter(&cont, buf.data); } diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index dbd339e9df4..5e7fe1d2f69 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -40,7 +40,7 @@ typedef struct CookedConstraint char *name; /* name, or NULL if none */ AttrNumber attnum; /* which attr (only for NOTNULL, DEFAULT) */ Node *expr; /* transformed default or check expr */ - bool is_enforced; /* is enforced? (only for CHECK) */ + bool is_enforced; /* is enforced? (for NOT NULL and CHECK) */ bool skip_validation; /* skip validation? (only for CHECK) */ bool is_local; /* constraint has local (non-inherited) def */ int16 inhcount; /* number of times constraint is inherited */ diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 4afceb5c692..fc581701c61 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -263,8 +263,8 @@ extern HeapTuple findNotNullConstraintAttnum(Oid relid, AttrNumber attnum); extern HeapTuple findNotNullConstraint(Oid relid, const char *colname); extern HeapTuple findDomainNotNullConstraint(Oid typid); extern AttrNumber extractNotNullColumn(HeapTuple constrTup); -extern bool AdjustNotNullInheritance(Oid relid, AttrNumber attnum, - bool is_local, bool is_no_inherit, bool is_notvalid); +extern bool AdjustNotNullInheritance(Oid relid, AttrNumber attnum, bool is_local, + bool is_no_inherit, bool is_notvalid, bool is_enforced); extern List *RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh); diff --git a/src/test/regress/expected/constraints.out b/src/test/regress/expected/constraints.out index 3590d3274f0..2291c017605 100644 --- a/src/test/regress/expected/constraints.out +++ b/src/test/regress/expected/constraints.out @@ -961,33 +961,135 @@ drop table notnull_tbl2, notnull_tbl3, notnull_tbl4, notnull_tbl5, notnull_tbl6; -- error cases: create table notnull_tbl_fail (a serial constraint foo not null constraint bar not null); ERROR: conflicting not-null constraint names "foo" and "bar" +create table notnull_tbl_fail (a serial constraint foo not null constraint bar not null not enforced); +ERROR: conflicting NOT ENFORCED declarations for not-null constraints on column "a" create table notnull_tbl_fail (a serial constraint foo not null no inherit constraint foo not null); ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a" +create table notnull_tbl_fail (a serial constraint foo not null not enforced); +ERROR: conflicting NOT ENFORCED declarations for not-null constraints on column "a" create table notnull_tbl_fail (a int constraint foo not null, constraint foo not null a no inherit); ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a" +create table notnull_tbl_fail (a int constraint foo not null, constraint foo not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a serial constraint foo not null, constraint bar not null a); ERROR: conflicting not-null constraint names "foo" and "bar" +create table notnull_tbl_fail (a serial constraint foo not null, constraint bar not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a serial, constraint foo not null a, constraint bar not null a); ERROR: conflicting not-null constraint names "foo" and "bar" +create table notnull_tbl_fail (a serial, constraint foo not null a, constraint bar not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a serial, constraint foo not null a no inherit); ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a" +create table notnull_tbl_fail (a serial, constraint foo not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a serial not null no inherit); ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a" +create table notnull_tbl_fail (a serial not null not enforced); +ERROR: conflicting NOT ENFORCED declarations for not-null constraints on column "a" create table notnull_tbl_fail (like notnull_tbl1, constraint foo2 not null a); ERROR: conflicting not-null constraint names "foo" and "foo2" +create table notnull_tbl_fail (like notnull_tbl1, constraint foo2 not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a int primary key constraint foo not null no inherit); ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a" +create table notnull_tbl_fail (a int primary key constraint foo not null not enforced); +ERROR: conflicting NOT ENFORCED declarations for not-null constraints on column "a" create table notnull_tbl_fail (a int not null no inherit primary key); ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a" create table notnull_tbl_fail (a int primary key, not null a no inherit); ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a" +create table notnull_tbl_fail (a int primary key, not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a int, primary key(a), not null a no inherit); ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a" +create table notnull_tbl_fail (a int, primary key(a), not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a int generated by default as identity, constraint foo not null a no inherit); ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a" +create table notnull_tbl_fail (a int generated by default as identity, constraint foo not null a not enforced); +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "a" create table notnull_tbl_fail (a int generated by default as identity not null no inherit); ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a" +create table notnull_tbl_fail (a int generated by default as identity not null not enforced); +ERROR: conflicting NOT ENFORCED declarations for not-null constraints on column "a" +alter table notnull_tbl1 add column b int not null not enforced; --ok +alter table notnull_tbl1 alter column b add generated always as identity; +ERROR: column "b" of relation "notnull_tbl1" must be declared NOT NULL before identity can be added +alter table notnull_tbl1 add column c int not null not enforced, alter column c add generated always as identity; +ERROR: column "c" of relation "notnull_tbl1" must be declared NOT NULL before identity can be added +alter table notnull_tbl1 add column c int generated always as identity not null not enforced; +ERROR: conflicting NOT ENFORCED declarations for not-null constraints on column "c" +alter table notnull_tbl1 add column c serial not null not enforced; +ERROR: conflicting NOT ENFORCED declarations for not-null constraints on column "c" +alter table notnull_tbl1 add column c serial, add constraint nn not null c not enforced; +ERROR: cannot change ENFORCED status of NOT NULL constraint "notnull_tbl1_c_not_null" on relation "notnull_tbl1" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. drop table notnull_tbl1; +-- NOT NULL [NOT] ENFORCED +CREATE TABLE ne_nn_tbl (x int, CONSTRAINT nn NOT NULL x NOT ENFORCED, NOT NULL x ENFORCED); --error +ERROR: conflicting NOT ENFORCED declaration for not-null constraint on column "x" +CREATE TABLE ne_nn_tbl (x int, CONSTRAINT nn NOT NULL x NOT ENFORCED); +ALTER TABLE ne_nn_tbl ALTER CONSTRAINT nn ENFORCED; --error +ERROR: cannot alter enforceability of constraint "nn" of relation "ne_nn_tbl" +ALTER TABLE ne_nn_tbl ALTER CONSTRAINT nn NOT ENFORCED; --error +ERROR: cannot alter enforceability of constraint "nn" of relation "ne_nn_tbl" +ALTER TABLE ne_nn_tbl VALIDATE CONSTRAINT nn; --error +ERROR: cannot validate NOT ENFORCED constraint +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'nn'; + pg_get_constraintdef +------------------------- + NOT NULL x NOT ENFORCED +(1 row) + +INSERT INTO ne_nn_tbl VALUES (NULL); --ok +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT ENFORCED NO INHERIT; --error +ERROR: cannot change NO INHERIT status of NOT NULL constraint "nn" on relation "ne_nn_tbl" +HINT: You might need to make the existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT. +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT VALID NOT ENFORCED; --no-op +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT ENFORCED; --no-op +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT VALID; --error +ERROR: cannot change ENFORCED status of NOT NULL constraint "nn" on relation "ne_nn_tbl" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced NOT NULL x ENFORCED; --error +ERROR: cannot change ENFORCED status of NOT NULL constraint "nn" on relation "ne_nn_tbl" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced NOT NULL x; --error, one column can only one not-null +ERROR: cannot change ENFORCED status of NOT NULL constraint "nn" on relation "ne_nn_tbl" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. +ALTER TABLE ne_nn_tbl ALTER COLUMN x SET NOT NULL; --error +ERROR: cannot validate NOT ENFORCED constraint "nn" on relation "ne_nn_tbl" +\d+ ne_nn_tbl + Table "public.ne_nn_tbl" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + x | integer | | | | plain | | +Not-null constraints: + "nn" NOT NULL "x" NOT ENFORCED + +TRUNCATE ne_nn_tbl; +--error, can not use not enforced not-null constaint for primary key +ALTER TABLE ne_nn_tbl ADD PRIMARY KEY(x); +ERROR: cannot create primary key on column "x" +DETAIL: The constraint "nn" on column "x" of table "ne_nn_tbl", marked NOT ENFORCED, is incompatible with a primary key. +HINT: You might need to drop it first using ALTER TABLE ... DROP CONSTRAINT. +ALTER TABLE ne_nn_tbl ADD column y int NOT NULL NOT ENFORCED ENFORCED; --error +ERROR: multiple ENFORCED/NOT ENFORCED clauses not allowed +LINE 1: ...E ne_nn_tbl ADD column y int NOT NULL NOT ENFORCED ENFORCED; + ^ +ALTER TABLE ne_nn_tbl ADD column x1 int NOT NULL NOT ENFORCED, ADD column y int NOT NULL ENFORCED; --ok +\d+ ne_nn_tbl + Table "public.ne_nn_tbl" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + x | integer | | | | plain | | + x1 | integer | | | | plain | | + y | integer | | not null | | plain | | +Not-null constraints: + "nn" NOT NULL "x" NOT ENFORCED + "ne_nn_tbl_x1_not_null" NOT NULL "x1" NOT ENFORCED + "ne_nn_tbl_y_not_null" NOT NULL "y" + -- NOT NULL NO INHERIT CREATE TABLE ATACC1 (a int, NOT NULL a NO INHERIT); CREATE TABLE ATACC2 () INHERITS (ATACC1); @@ -1668,6 +1770,66 @@ COMMENT ON CONSTRAINT constr_parent2_a_not_null ON constr_parent2 IS 'this const COMMENT ON CONSTRAINT constr_parent2_a_not_null ON constr_child2 IS 'this constraint is valid'; DEALLOCATE get_nnconstraint_info; -- end NOT NULL NOT VALID +-- Verify NOT NULL ENFORCED / ENFORCED with partition table. +PREPARE get_nnconstraint_info(regclass[]) AS +SELECT conrelid::regclass as relname, conname, convalidated, conislocal, coninhcount, conenforced +FROM pg_constraint +WHERE conrelid = ANY($1) +ORDER BY conrelid::regclass::text COLLATE "C", conname; +-- partitioned table have enforced not-null, then partitions can not have not enforced not-null +CREATE TABLE pp_nn (a int, b int, NOT NULL a) PARTITION BY LIST (a); +CREATE TABLE pp_nn_1(a int, b int, CONSTRAINT nn1 NOT NULL a NOT ENFORCED); +ALTER TABLE pp_nn ATTACH PARTITION pp_nn_1 FOR VALUES IN (NULL,5); --error +ERROR: constraint "nn1" conflicts with NOT ENFORCED constraint on child table "pp_nn_1" +DROP TABLE pp_nn, pp_nn_1; +CREATE TABLE notnull_tbl1 (a int, b int, CONSTRAINT nn0 NOT NULL a NOT ENFORCED) PARTITION BY LIST (a); +CREATE TABLE notnull_tbl1_1 PARTITION OF notnull_tbl1 FOR VALUES IN (1,2); +-- if partitioned table not-null is not enforced, then partitions can have enforced +-- or not enforced not-null +CREATE TABLE notnull_tbl1_2(a int, CONSTRAINT nn1 NOT NULL a, b int); +ALTER TABLE notnull_tbl1 ATTACH PARTITION notnull_tbl1_2 FOR VALUES IN (3,4); --ok +CREATE TABLE notnull_tbl1_3(a int, b int, CONSTRAINT nn2 NOT NULL a NOT ENFORCED); +ALTER TABLE notnull_tbl1 ATTACH PARTITION notnull_tbl1_3 FOR VALUES IN (NULL,5); +CREATE TABLE notnull_tbl1_4(a int, b int); +ALTER TABLE notnull_tbl1 ATTACH PARTITION notnull_tbl1_4 FOR VALUES IN (6); --error +ERROR: column "a" in child table "notnull_tbl1_4" must be marked NOT NULL NOT ENFORCED +EXECUTE get_nnconstraint_info('{notnull_tbl1, notnull_tbl1_1, notnull_tbl1_2, notnull_tbl1_3}'); + relname | conname | convalidated | conislocal | coninhcount | conenforced +----------------+---------+--------------+------------+-------------+------------- + notnull_tbl1 | nn0 | f | t | 0 | f + notnull_tbl1_1 | nn0 | f | f | 1 | f + notnull_tbl1_2 | nn1 | t | f | 1 | t + notnull_tbl1_3 | nn2 | f | f | 1 | f +(4 rows) + +ALTER TABLE notnull_tbl1 ALTER COLUMN a SET NOT NULL; --error, can not validate not-enforced +ERROR: cannot validate NOT ENFORCED constraint "nn0" on relation "notnull_tbl1" +ALTER TABLE notnull_tbl1 VALIDATE CONSTRAINT nn0; --error, can not validate not-enforced +ERROR: cannot validate NOT ENFORCED constraint +DROP TABLE notnull_tbl1, notnull_tbl1_4; +-- Create table with NOT NULL NOT ENFORCED constraint, for pg_upgrade. +CREATE TABLE nn_notenforced (a int, b int); +INSERT INTO nn_notenforced VALUES (NULL, 1), (NULL, 2), (300, 3); +ALTER TABLE nn_notenforced ADD CONSTRAINT nn NOT NULL a NOT ENFORCED; +EXECUTE get_nnconstraint_info('{nn_notenforced}'); + relname | conname | convalidated | conislocal | coninhcount | conenforced +----------------+---------+--------------+------------+-------------+------------- + nn_notenforced | nn | f | t | 0 | f +(1 row) + +-- Inherit test for pg_upgrade +CREATE TABLE notenforced_nn_parent (a int); +CREATE TABLE notenforced_nn_child () INHERITS (notenforced_nn_parent); +ALTER TABLE notenforced_nn_parent ADD CONSTRAINT nn NOT NULL a NOT ENFORCED; +EXECUTE get_nnconstraint_info('{notenforced_nn_parent, notenforced_nn_child}'); + relname | conname | convalidated | conislocal | coninhcount | conenforced +-----------------------+---------+--------------+------------+-------------+------------- + notenforced_nn_child | nn | f | f | 1 | f + notenforced_nn_parent | nn | f | t | 0 | f +(2 rows) + +DEALLOCATE get_nnconstraint_info; +--end of NOT NULL ENFORCED / ENFORCED with partition table. -- Comments -- Setup a low-level role to enforce non-superuser checks. CREATE ROLE regress_constraint_comments; diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index 29a779c2e90..993fd2a6b3b 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -317,6 +317,28 @@ Referenced by: TABLE "inhz" CONSTRAINT "inhz_x_fkey" FOREIGN KEY (x) REFERENCES inhz(xx) DROP TABLE inhz; +--not null not enforced constraint +CREATE TABLE not_enforced_nn (a text, constraint nn not null a not enforced); +COMMENT ON CONSTRAINT nn ON not_enforced_nn is 'not enforced not null constraint comment test'; +CREATE TABLE not_enforced_nn_copy(LIKE not_enforced_nn INCLUDING CONSTRAINTS INCLUDING COMMENTS); +\d+ not_enforced_nn_copy + Table "public.not_enforced_nn_copy" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+------+-----------+----------+---------+----------+--------------+------------- + a | text | | | | extended | | +Not-null constraints: + "nn" NOT NULL "a" NOT ENFORCED + +SELECT conname, description +FROM pg_description, pg_constraint c +WHERE classoid = 'pg_constraint'::regclass +AND objoid = c.oid AND c.conrelid = 'not_enforced_nn_copy'::regclass +ORDER BY conname COLLATE "C"; + conname | description +---------+----------------------------------------------- + nn | not enforced not null constraint comment test +(1 row) + -- including storage and comments CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) ENFORCED PRIMARY KEY, b text CHECK (length(b) > 100) NOT ENFORCED); diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 5b5055babdc..13008b71570 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1439,6 +1439,113 @@ alter table p1_c1 inherit p1; ERROR: constraint "p1_a_check" conflicts with NOT ENFORCED constraint on child table "p1_c1" drop table p1, p1_c1; -- +-- Similarly, check the merging of existing constraints; a parent not-null constraint +-- marked as NOT ENFORCED can merge with an ENFORCED child constraint, but the +-- reverse is not allowed. +-- +create table p1(f1 int constraint p1_a_nn not null); +create table p1_c1(f1 int constraint p1_c1_nn not null not enforced); +alter table p1_c1 inherit p1; --error +ERROR: constraint "p1_c1_nn" conflicts with NOT ENFORCED constraint on child table "p1_c1" +create table p1_c2(f1 int not null not enforced) inherits(p1); --error +NOTICE: merging column "f1" with inherited definition +ERROR: cannot define not-null constraint with NOT ENFORCED on column "f1" +DETAIL: The column has an inherited ENFORCED not-null constraint. +drop table if exists p1, p1_c1; +create table p1(f1 int constraint p1_a_nn not null not enforced); +create table p1_c1(f1 int constraint p1_c1_a_nn not null); +alter table p1_c1 inherit p1; --ok +create table p1_c3() inherits(p1, p1_c1); --error +NOTICE: merging multiple inherited definitions of column "f1" +ERROR: cannot define not-null constraint on column "p1_a_nn" +DETAIL: The column inherited not-null constraints have conflict ENFORCED status. +create table p1_c3(f1 int); +alter table p1_c3 inherit p1; --error +ERROR: column "f1" in child table "p1_c3" must be marked NOT NULL NOT ENFORCED +--error, can not merge conflict not-null constraint +create table p1_c4(f1 int not null not enforced) inherits(p1, p1_c1); +NOTICE: merging multiple inherited definitions of column "f1" +NOTICE: merging column "f1" with inherited definition +ERROR: cannot define not-null constraint with NOT ENFORCED on column "f1" +DETAIL: The column has an inherited ENFORCED not-null constraint. +create table p1_c4(f1 int not null) inherits(p1, p1_c1); --ok +NOTICE: merging multiple inherited definitions of column "f1" +NOTICE: merging column "f1" with inherited definition +create table p1_c5(f1 int) inherits(p1, p1_c1); --error +NOTICE: merging multiple inherited definitions of column "f1" +NOTICE: merging column "f1" with inherited definition +ERROR: cannot define not-null constraint on column "p1_a_nn" +DETAIL: The column inherited not-null constraints have conflict ENFORCED status. +drop table if exists p1, p1_c3 cascade; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table p1_c1 +drop cascades to table p1_c4 +create table p1(f1 int); +create table p1_c1() inherits(p1); +alter table p1 add constraint p1_nn_1 not null f1 not enforced; +alter table p1_c1 add constraint p1_c1_nn_1 not null f1 enforced; --error, f1 already have not-null constraint +ERROR: cannot change ENFORCED status of NOT NULL constraint "p1_nn_1" on relation "p1_c1" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. +create table p1_nenn(f1 int constraint p1_nn not null) inherits(p1, p1_c1); --ok +NOTICE: merging multiple inherited definitions of column "f1" +NOTICE: merging column "f1" with inherited definition +\d+ p1_nenn + Table "public.p1_nenn" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + f1 | integer | | not null | | plain | | +Not-null constraints: + "p1_nn" NOT NULL "f1" (local, inherited) +Inherits: p1, + p1_c1 + +drop table p1_nenn; +-- not allowed: child is not enforced, parent have enforced +alter table p1 alter column f1 drop not null; +alter table p1_c1 add constraint nn not null f1 not enforced; +alter table p1 add constraint nn not null f1 enforced; --error +ERROR: cannot change ENFORCED status of NOT NULL constraint "nn" on relation "p1_c1" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. +alter table p1_c1 alter column f1 drop not null; +alter table p1_c1 add constraint nn not null f1 not valid enforced; +alter table p1 add constraint nn not null f1 not enforced; --error +ERROR: cannot change ENFORCED status of NOT NULL constraint "nn" on relation "p1_c1" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. +drop table p1 cascade; +NOTICE: drop cascades to table p1_c1 +-- Test INHERIT for not enforced constraint. +create table inh_nn1 (f1 int, constraint nn not null f1 no inherit not enforced); +create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1); +create table inh_nn3 () inherits (inh_nn2); +create table inh_nn4 () inherits (inh_nn1); +alter table inh_nn2 add constraint nn not null f1 not enforced; +alter table inh_nn1 alter constraint nn inherit; --ok. +select conrelid::regclass, conname, contype, conenforced, convalidated, coninhcount, connoinherit, conislocal +from pg_constraint +where conrelid::regclass::text = ANY ('{inh_nn1, inh_nn2, inh_nn3, inh_nn4}') +order by conname, conrelid::regclass::text collate "C"; + conrelid | conname | contype | conenforced | convalidated | coninhcount | connoinherit | conislocal +----------+---------+---------+-------------+--------------+-------------+--------------+------------ + inh_nn1 | nn | n | f | f | 0 | f | t + inh_nn2 | nn | n | f | f | 1 | f | t + inh_nn3 | nn | n | f | f | 1 | f | f + inh_nn4 | nn | n | f | f | 1 | f | f +(4 rows) + +drop table inh_nn1 cascade; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table inh_nn2 +drop cascades to table inh_nn3 +drop cascades to table inh_nn4 +create table inh_nn1 (f1 int, constraint nn not null f1 no inherit not enforced); +create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1); +alter table inh_nn2 add constraint nn not null f1; +alter table inh_nn1 alter constraint nn inherit; --error +ERROR: cannot change ENFORCED status of NOT NULL constraint "nn" on relation "inh_nn2" +HINT: You might need to drop the existing not enforced constraint using ALTER TABLE ... DROP CONSTRAINT. +drop table inh_nn1 cascade; +NOTICE: drop cascades to table inh_nn2 +-- -- Test DROP behavior of multiply-defined CHECK constraints -- create table p1(f1 int constraint f1_pos CHECK (f1 > 0)); diff --git a/src/test/regress/sql/constraints.sql b/src/test/regress/sql/constraints.sql index 1f6dc8fd69f..2723c754774 100644 --- a/src/test/regress/sql/constraints.sql +++ b/src/test/regress/sql/constraints.sql @@ -663,22 +663,67 @@ drop table notnull_tbl2, notnull_tbl3, notnull_tbl4, notnull_tbl5, notnull_tbl6; -- error cases: create table notnull_tbl_fail (a serial constraint foo not null constraint bar not null); +create table notnull_tbl_fail (a serial constraint foo not null constraint bar not null not enforced); create table notnull_tbl_fail (a serial constraint foo not null no inherit constraint foo not null); +create table notnull_tbl_fail (a serial constraint foo not null not enforced); create table notnull_tbl_fail (a int constraint foo not null, constraint foo not null a no inherit); +create table notnull_tbl_fail (a int constraint foo not null, constraint foo not null a not enforced); create table notnull_tbl_fail (a serial constraint foo not null, constraint bar not null a); +create table notnull_tbl_fail (a serial constraint foo not null, constraint bar not null a not enforced); create table notnull_tbl_fail (a serial, constraint foo not null a, constraint bar not null a); +create table notnull_tbl_fail (a serial, constraint foo not null a, constraint bar not null a not enforced); create table notnull_tbl_fail (a serial, constraint foo not null a no inherit); +create table notnull_tbl_fail (a serial, constraint foo not null a not enforced); create table notnull_tbl_fail (a serial not null no inherit); +create table notnull_tbl_fail (a serial not null not enforced); create table notnull_tbl_fail (like notnull_tbl1, constraint foo2 not null a); +create table notnull_tbl_fail (like notnull_tbl1, constraint foo2 not null a not enforced); create table notnull_tbl_fail (a int primary key constraint foo not null no inherit); +create table notnull_tbl_fail (a int primary key constraint foo not null not enforced); create table notnull_tbl_fail (a int not null no inherit primary key); create table notnull_tbl_fail (a int primary key, not null a no inherit); +create table notnull_tbl_fail (a int primary key, not null a not enforced); create table notnull_tbl_fail (a int, primary key(a), not null a no inherit); +create table notnull_tbl_fail (a int, primary key(a), not null a not enforced); create table notnull_tbl_fail (a int generated by default as identity, constraint foo not null a no inherit); +create table notnull_tbl_fail (a int generated by default as identity, constraint foo not null a not enforced); create table notnull_tbl_fail (a int generated by default as identity not null no inherit); +create table notnull_tbl_fail (a int generated by default as identity not null not enforced); +alter table notnull_tbl1 add column b int not null not enforced; --ok +alter table notnull_tbl1 alter column b add generated always as identity; +alter table notnull_tbl1 add column c int not null not enforced, alter column c add generated always as identity; +alter table notnull_tbl1 add column c int generated always as identity not null not enforced; +alter table notnull_tbl1 add column c serial not null not enforced; +alter table notnull_tbl1 add column c serial, add constraint nn not null c not enforced; drop table notnull_tbl1; +-- NOT NULL [NOT] ENFORCED +CREATE TABLE ne_nn_tbl (x int, CONSTRAINT nn NOT NULL x NOT ENFORCED, NOT NULL x ENFORCED); --error +CREATE TABLE ne_nn_tbl (x int, CONSTRAINT nn NOT NULL x NOT ENFORCED); +ALTER TABLE ne_nn_tbl ALTER CONSTRAINT nn ENFORCED; --error +ALTER TABLE ne_nn_tbl ALTER CONSTRAINT nn NOT ENFORCED; --error +ALTER TABLE ne_nn_tbl VALIDATE CONSTRAINT nn; --error + +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'nn'; +INSERT INTO ne_nn_tbl VALUES (NULL); --ok + +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT ENFORCED NO INHERIT; --error +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT VALID NOT ENFORCED; --no-op +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT ENFORCED; --no-op +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced1 NOT NULL x NOT VALID; --error +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced NOT NULL x ENFORCED; --error +ALTER TABLE ne_nn_tbl ADD CONSTRAINT nn_enforced NOT NULL x; --error, one column can only one not-null +ALTER TABLE ne_nn_tbl ALTER COLUMN x SET NOT NULL; --error +\d+ ne_nn_tbl + +TRUNCATE ne_nn_tbl; +--error, can not use not enforced not-null constaint for primary key +ALTER TABLE ne_nn_tbl ADD PRIMARY KEY(x); +ALTER TABLE ne_nn_tbl ADD column y int NOT NULL NOT ENFORCED ENFORCED; --error +ALTER TABLE ne_nn_tbl ADD column x1 int NOT NULL NOT ENFORCED, ADD column y int NOT NULL ENFORCED; --ok +\d+ ne_nn_tbl + -- NOT NULL NO INHERIT CREATE TABLE ATACC1 (a int, NOT NULL a NO INHERIT); CREATE TABLE ATACC2 () INHERITS (ATACC1); @@ -1008,6 +1053,49 @@ DEALLOCATE get_nnconstraint_info; -- end NOT NULL NOT VALID +-- Verify NOT NULL ENFORCED / ENFORCED with partition table. +PREPARE get_nnconstraint_info(regclass[]) AS +SELECT conrelid::regclass as relname, conname, convalidated, conislocal, coninhcount, conenforced +FROM pg_constraint +WHERE conrelid = ANY($1) +ORDER BY conrelid::regclass::text COLLATE "C", conname; + +-- partitioned table have enforced not-null, then partitions can not have not enforced not-null +CREATE TABLE pp_nn (a int, b int, NOT NULL a) PARTITION BY LIST (a); +CREATE TABLE pp_nn_1(a int, b int, CONSTRAINT nn1 NOT NULL a NOT ENFORCED); +ALTER TABLE pp_nn ATTACH PARTITION pp_nn_1 FOR VALUES IN (NULL,5); --error +DROP TABLE pp_nn, pp_nn_1; + +CREATE TABLE notnull_tbl1 (a int, b int, CONSTRAINT nn0 NOT NULL a NOT ENFORCED) PARTITION BY LIST (a); +CREATE TABLE notnull_tbl1_1 PARTITION OF notnull_tbl1 FOR VALUES IN (1,2); +-- if partitioned table not-null is not enforced, then partitions can have enforced +-- or not enforced not-null +CREATE TABLE notnull_tbl1_2(a int, CONSTRAINT nn1 NOT NULL a, b int); +ALTER TABLE notnull_tbl1 ATTACH PARTITION notnull_tbl1_2 FOR VALUES IN (3,4); --ok +CREATE TABLE notnull_tbl1_3(a int, b int, CONSTRAINT nn2 NOT NULL a NOT ENFORCED); +ALTER TABLE notnull_tbl1 ATTACH PARTITION notnull_tbl1_3 FOR VALUES IN (NULL,5); +CREATE TABLE notnull_tbl1_4(a int, b int); +ALTER TABLE notnull_tbl1 ATTACH PARTITION notnull_tbl1_4 FOR VALUES IN (6); --error + +EXECUTE get_nnconstraint_info('{notnull_tbl1, notnull_tbl1_1, notnull_tbl1_2, notnull_tbl1_3}'); +ALTER TABLE notnull_tbl1 ALTER COLUMN a SET NOT NULL; --error, can not validate not-enforced +ALTER TABLE notnull_tbl1 VALIDATE CONSTRAINT nn0; --error, can not validate not-enforced +DROP TABLE notnull_tbl1, notnull_tbl1_4; + +-- Create table with NOT NULL NOT ENFORCED constraint, for pg_upgrade. +CREATE TABLE nn_notenforced (a int, b int); +INSERT INTO nn_notenforced VALUES (NULL, 1), (NULL, 2), (300, 3); +ALTER TABLE nn_notenforced ADD CONSTRAINT nn NOT NULL a NOT ENFORCED; +EXECUTE get_nnconstraint_info('{nn_notenforced}'); + +-- Inherit test for pg_upgrade +CREATE TABLE notenforced_nn_parent (a int); +CREATE TABLE notenforced_nn_child () INHERITS (notenforced_nn_parent); +ALTER TABLE notenforced_nn_parent ADD CONSTRAINT nn NOT NULL a NOT ENFORCED; +EXECUTE get_nnconstraint_info('{notenforced_nn_parent, notenforced_nn_child}'); +DEALLOCATE get_nnconstraint_info; +--end of NOT NULL ENFORCED / ENFORCED with partition table. + -- Comments -- Setup a low-level role to enforce non-superuser checks. CREATE ROLE regress_constraint_comments; diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index bf8702116a7..d1a0613aa4d 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -127,6 +127,18 @@ CREATE TABLE inhz (x text REFERENCES inhz, LIKE inhx INCLUDING INDEXES); \d inhz DROP TABLE inhz; +--not null not enforced constraint +CREATE TABLE not_enforced_nn (a text, constraint nn not null a not enforced); +COMMENT ON CONSTRAINT nn ON not_enforced_nn is 'not enforced not null constraint comment test'; +CREATE TABLE not_enforced_nn_copy(LIKE not_enforced_nn INCLUDING CONSTRAINTS INCLUDING COMMENTS); +\d+ not_enforced_nn_copy + +SELECT conname, description +FROM pg_description, pg_constraint c +WHERE classoid = 'pg_constraint'::regclass +AND objoid = c.oid AND c.conrelid = 'not_enforced_nn_copy'::regclass +ORDER BY conname COLLATE "C"; + -- including storage and comments CREATE TABLE ctlt1 (a text CHECK (length(a) > 2) ENFORCED PRIMARY KEY, b text CHECK (length(b) > 100) NOT ENFORCED); diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 699e8ac09c8..afe6d048d38 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -527,6 +527,68 @@ create table p1_c1(f1 int constraint p1_a_check check (f1 > 0) not enforced); alter table p1_c1 inherit p1; drop table p1, p1_c1; + +-- +-- Similarly, check the merging of existing constraints; a parent not-null constraint +-- marked as NOT ENFORCED can merge with an ENFORCED child constraint, but the +-- reverse is not allowed. +-- +create table p1(f1 int constraint p1_a_nn not null); +create table p1_c1(f1 int constraint p1_c1_nn not null not enforced); +alter table p1_c1 inherit p1; --error +create table p1_c2(f1 int not null not enforced) inherits(p1); --error +drop table if exists p1, p1_c1; + +create table p1(f1 int constraint p1_a_nn not null not enforced); +create table p1_c1(f1 int constraint p1_c1_a_nn not null); +alter table p1_c1 inherit p1; --ok +create table p1_c3() inherits(p1, p1_c1); --error +create table p1_c3(f1 int); +alter table p1_c3 inherit p1; --error +--error, can not merge conflict not-null constraint +create table p1_c4(f1 int not null not enforced) inherits(p1, p1_c1); +create table p1_c4(f1 int not null) inherits(p1, p1_c1); --ok +create table p1_c5(f1 int) inherits(p1, p1_c1); --error +drop table if exists p1, p1_c3 cascade; + +create table p1(f1 int); +create table p1_c1() inherits(p1); +alter table p1 add constraint p1_nn_1 not null f1 not enforced; +alter table p1_c1 add constraint p1_c1_nn_1 not null f1 enforced; --error, f1 already have not-null constraint +create table p1_nenn(f1 int constraint p1_nn not null) inherits(p1, p1_c1); --ok +\d+ p1_nenn +drop table p1_nenn; + +-- not allowed: child is not enforced, parent have enforced +alter table p1 alter column f1 drop not null; +alter table p1_c1 add constraint nn not null f1 not enforced; +alter table p1 add constraint nn not null f1 enforced; --error + +alter table p1_c1 alter column f1 drop not null; +alter table p1_c1 add constraint nn not null f1 not valid enforced; +alter table p1 add constraint nn not null f1 not enforced; --error +drop table p1 cascade; + +-- Test INHERIT for not enforced constraint. +create table inh_nn1 (f1 int, constraint nn not null f1 no inherit not enforced); +create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1); +create table inh_nn3 () inherits (inh_nn2); +create table inh_nn4 () inherits (inh_nn1); +alter table inh_nn2 add constraint nn not null f1 not enforced; +alter table inh_nn1 alter constraint nn inherit; --ok. + +select conrelid::regclass, conname, contype, conenforced, convalidated, coninhcount, connoinherit, conislocal +from pg_constraint +where conrelid::regclass::text = ANY ('{inh_nn1, inh_nn2, inh_nn3, inh_nn4}') +order by conname, conrelid::regclass::text collate "C"; + +drop table inh_nn1 cascade; +create table inh_nn1 (f1 int, constraint nn not null f1 no inherit not enforced); +create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1); +alter table inh_nn2 add constraint nn not null f1; +alter table inh_nn1 alter constraint nn inherit; --error +drop table inh_nn1 cascade; + -- -- Test DROP behavior of multiply-defined CHECK constraints -- -- 2.34.1