From 5a66a8da2a8c5fef88d7730e127900e5b83e0cde Mon Sep 17 00:00:00 2001 From: jian he Date: Sun, 16 Mar 2025 20:50:53 +0800 Subject: [PATCH v4 1/1] not null not valid implementation --- src/backend/catalog/heap.c | 14 +- src/backend/catalog/index.c | 1 + src/backend/catalog/pg_constraint.c | 3 + src/backend/commands/tablecmds.c | 168 +++++++++++++++++++++- src/backend/commands/trigger.c | 1 + src/backend/commands/typecmds.c | 2 + src/backend/parser/gram.y | 5 +- src/backend/parser/parse_utilcmd.c | 63 +++++++- src/include/catalog/heap.h | 1 + src/include/catalog/pg_constraint.h | 3 + src/include/nodes/parsenodes.h | 1 + src/test/regress/expected/constraints.out | 63 ++++++++ src/test/regress/sql/constraints.sql | 41 ++++++ 13 files changed, 355 insertions(+), 11 deletions(-) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index bd3554c0bfd..6dc0267bd5a 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -104,7 +104,7 @@ static ObjectAddress AddNewRelationType(const char *typeName, static void RelationRemoveInheritance(Oid relid); static Oid StoreRelCheck(Relation rel, const char *ccname, Node *expr, bool is_enforced, bool is_validated, bool is_local, - int16 inhcount, bool is_no_inherit, bool is_internal); + int16 inhcount, bool is_no_inherit, char coninternaltype, bool is_internal); static void StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal); static bool MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr, @@ -2136,7 +2136,7 @@ SetAttrMissing(Oid relid, char *attname, char *value) static Oid StoreRelCheck(Relation rel, const char *ccname, Node *expr, bool is_enforced, bool is_validated, bool is_local, - int16 inhcount, bool is_no_inherit, bool is_internal) + int16 inhcount, bool is_no_inherit, char coninternaltype, bool is_internal) { char *ccbin; List *varList; @@ -2228,6 +2228,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr, inhcount, /* coninhcount */ is_no_inherit, /* connoinherit */ false, /* conperiod */ + coninternaltype, /* coninternaltype */ is_internal); /* internally constructed? */ pfree(ccbin); @@ -2282,6 +2283,7 @@ StoreRelNotNull(Relation rel, const char *nnname, AttrNumber attnum, inhcount, is_no_inherit, false, + '\0', /* coninternaltype */ false); return constrOid; } @@ -2327,7 +2329,9 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) StoreRelCheck(rel, con->name, con->expr, con->is_enforced, !con->skip_validation, con->is_local, con->inhcount, - con->is_no_inherit, is_internal); + con->is_no_inherit, + con->coninternaltype, + is_internal); numchecks++; break; @@ -2460,6 +2464,7 @@ AddRelationNewConstraints(Relation rel, cooked->is_local = is_local; cooked->inhcount = is_local ? 0 : 1; cooked->is_no_inherit = false; + cooked->coninternaltype = '\0'; cookedConstraints = lappend(cookedConstraints, cooked); } @@ -2579,6 +2584,7 @@ AddRelationNewConstraints(Relation rel, StoreRelCheck(rel, ccname, expr, cdef->is_enforced, cdef->initially_valid, is_local, is_local ? 0 : 1, cdef->is_no_inherit, + cdef->contype_internal, is_internal); numchecks++; @@ -2594,6 +2600,7 @@ AddRelationNewConstraints(Relation rel, cooked->is_local = is_local; cooked->inhcount = is_local ? 0 : 1; cooked->is_no_inherit = cdef->is_no_inherit; + cooked->coninternaltype = cdef->contype_internal; cookedConstraints = lappend(cookedConstraints, cooked); } else if (cdef->contype == CONSTR_NOTNULL) @@ -2670,6 +2677,7 @@ AddRelationNewConstraints(Relation rel, nncooked->is_local = is_local; nncooked->inhcount = inhcount; nncooked->is_no_inherit = cdef->is_no_inherit; + nncooked->coninternaltype = '\0'; cookedConstraints = lappend(cookedConstraints, nncooked); } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 739a92bdcc1..e5bc679a850 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1986,6 +1986,7 @@ index_constraint_create(Relation heapRelation, inhcount, noinherit, is_without_overlaps, + '\0', /* coninternaltype */ is_internal); /* diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index ac80652baf2..3f3a276e3aa 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -80,6 +80,7 @@ CreateConstraintEntry(const char *constraintName, int16 conInhCount, bool conNoInherit, bool conPeriod, + char coninternaltype, bool is_internal) { Relation conDesc; @@ -202,6 +203,7 @@ CreateConstraintEntry(const char *constraintName, values[Anum_pg_constraint_coninhcount - 1] = Int16GetDatum(conInhCount); values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit); values[Anum_pg_constraint_conperiod - 1] = BoolGetDatum(conPeriod); + values[Anum_pg_constraint_coninternaltype - 1] = CharGetDatum(coninternaltype); if (conkeyArray) values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray); @@ -834,6 +836,7 @@ RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh) cooked->is_local = true; cooked->inhcount = 0; cooked->is_no_inherit = conForm->connoinherit; + cooked->coninternaltype = '\0'; notnulls = lappend(notnulls, cooked); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 129c97fdf28..b3df5b913a5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -604,6 +604,9 @@ static void GetForeignKeyCheckTriggers(Relation trigrel, static void ATExecDropConstraint(Relation rel, const char *constrName, DropBehavior behavior, bool recurse, bool missing_ok, LOCKMODE lockmode); +static void DropConstraintByOid(Relation rel, Oid constroid, + DropBehavior behavior, bool recurse, + bool missing_ok, LOCKMODE lockmode); static ObjectAddress dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior behavior, bool recurse, bool recursing, @@ -991,6 +994,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, cooked->is_local = true; /* not used for defaults */ cooked->inhcount = 0; /* ditto */ cooked->is_no_inherit = false; + cooked->coninternaltype = '\0'; cookedDefaults = lappend(cookedDefaults, cooked); } } @@ -10581,6 +10585,7 @@ addFkConstraint(addFkConstraintSides fkside, coninhcount, /* inhcount */ connoinherit, /* conNoInherit */ with_period, /* conPeriod */ + '\0', /* coninternaltype */ is_internal); /* is_internal */ ObjectAddressSet(address, ConstraintRelationId, constrOid); @@ -12337,6 +12342,9 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName, HeapTuple tuple; Form_pg_constraint con; ObjectAddress address; + Oid constroid = InvalidOid; + AttrNumber attnum = -1; + char *conname = NULL; conrel = table_open(ConstraintRelationId, RowExclusiveLock); @@ -12367,7 +12375,8 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName, con = (Form_pg_constraint) GETSTRUCT(tuple); if (con->contype != CONSTRAINT_FOREIGN && - con->contype != CONSTRAINT_CHECK) + con->contype != CONSTRAINT_CHECK && + con->contype != CONSTRAINT_NOTNULL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint", @@ -12383,14 +12392,30 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName, if (con->contype == CONSTRAINT_FOREIGN) { QueueFKConstraintValidation(wqueue, conrel, rel, tuple, lockmode); + ObjectAddressSet(address, ConstraintRelationId, con->oid); } - else if (con->contype == CONSTRAINT_CHECK) + else if (con->contype == CONSTRAINT_CHECK && con->coninternaltype == '\0') { QueueCheckConstraintValidation(wqueue, conrel, rel, constrName, tuple, recurse, recursing, lockmode); + ObjectAddressSet(address, ConstraintRelationId, con->oid); + } + else if (con->contype == CONSTRAINT_CHECK && con->coninternaltype == CONSTRAINT_NOTNULL) + { + Datum adatum; + ArrayType *arr; + adatum = SysCacheGetAttrNotNull(CONSTROID, tuple, + Anum_pg_constraint_conkey); + arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != 1 || + ARR_HASNULL(arr) || + ARR_ELEMTYPE(arr) != INT2OID) + elog(ERROR, "confkey is not a 1-D smallint array"); + memcpy(&attnum, ARR_DATA_PTR(arr), 1 * sizeof(int16)); + constroid = con->oid; + conname = pstrdup(NameStr(con->conname)); } - - ObjectAddressSet(address, ConstraintRelationId, con->oid); } else address = InvalidObjectAddress; /* already validated */ @@ -12399,6 +12424,80 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName, table_close(conrel, RowExclusiveLock); + if (OidIsValid(constroid)) + { + AlteredTableInfo *tab; + Constraint *n; + String *val; + List *children; + + if (rel->rd_rel->relispartition) + { + char *relname; + List *ancestors = NIL; + Oid rootrelid = InvalidOid; + + ancestors = get_partition_ancestors(RelationGetRelid(rel)); + Assert(ancestors != NIL); + + rootrelid = llast_oid(ancestors); + list_free(ancestors); + + relname = get_rel_name(rootrelid); + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot validate not-null constraint from partition \"%s\"", + RelationGetRelationName(rel)), + errhint("You might need validate not-null constraint through root partition \"%s\"", relname)); + } + else if (has_superclass(RelationGetRelid(rel))) + { + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot validate not-null constraint on inheritance children \"%s\"", + RelationGetRelationName(rel))); + } + + children = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL); + DropConstraintByOid(rel, constroid, + DROP_CASCADE, true, + false, lockmode); + + tab = ATGetQueueEntry(wqueue, rel); + n = makeNode(Constraint); + n->contype = CONSTR_NOTNULL; + n->conname = conname; + n->location = -1; + n->is_no_inherit = false; + n->raw_expr = NULL; + n->is_enforced = true; + n->initially_valid = true; + n->skip_validation = false; + val = makeString(get_attname(RelationGetRelid(rel), attnum, + false)); + n->keys = list_make1(val); + ATAddCheckNNConstraint(wqueue, tab, rel, n, + recurse, recursing, false, lockmode); + + /* + * DropConstraintByOid will call RemoveConstraintById. + * Inside RemoveConstraintById, the relation to which this constraint belongs + * will be locked in AccessExclusiveLock mode. + * + * ATAddCheckNNConstraint will install not-null constraint for rel and it's children. + * ALTER TABLE VALIDATE CONSTRAINT generally take ShareUpdateExclusiveLock lock. + * removing the logical equivalent CHECK constraint will not impact the data integrity. + * thus here release the AccessExclusiveLock on rel and its children. + */ + foreach_oid(childrelid, children) + UnlockRelationOid(childrelid, AccessExclusiveLock); + + if (CheckRelationLockedByMe(rel, AccessExclusiveLock, true)) + elog(ERROR, "relation \"%s\" was lock in AccessExclusiveLock", RelationGetRelationName(rel)); + + address = InvalidObjectAddress; + } + return address; } @@ -13298,6 +13397,67 @@ createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid, parentUpdTrigger, false); } + + +/* + * DROP CONSTRAINT by Oid + * + * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism. + * The constroid is the OID of the constraint to be dropped. If the constraint + * has any inherited child constraints, it will recursively drop them too. + */ +static void +DropConstraintByOid(Relation rel, Oid constroid, + DropBehavior behavior, bool recurse, + bool missing_ok, LOCKMODE lockmode) +{ + Relation conrel; + SysScanDesc scan; + ScanKeyData skey[1]; + HeapTuple tuple; + bool found = false; + char *constrName = NULL; + Form_pg_constraint con; + + conrel = table_open(ConstraintRelationId, RowExclusiveLock); + /* + * Find and drop the target constraint + */ + ScanKeyInit(&skey[0], + Anum_pg_constraint_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(constroid)); + scan = systable_beginscan(conrel, ConstraintOidIndexId, + true, NULL, 1, skey); + + /* There can be at most one matching row */ + if (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + con = (Form_pg_constraint) GETSTRUCT(tuple); + constrName = pstrdup(NameStr(con->conname)); + dropconstraint_internal(rel, tuple, behavior, recurse, false, + missing_ok, lockmode); + found = true; + } + + systable_endscan(scan); + + if (!found) + { + if (!missing_ok) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("constraint \"%s\" of relation \"%s\" does not exist", + constrName, RelationGetRelationName(rel))); + else + ereport(NOTICE, + errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping", + constrName, RelationGetRelationName(rel))); + } + + table_close(conrel, RowExclusiveLock); +} + /* * ALTER TABLE DROP CONSTRAINT * diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index c9f61130c69..e99b91d3840 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -839,6 +839,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, 0, /* inhcount */ true, /* noinherit */ false, /* conperiod */ + '\0', /* coninternaltype */ isInternal); /* is_internal */ } diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 3cb3ca1cca1..457d47b7ba4 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -3606,6 +3606,7 @@ domainAddCheckConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, 0, /* inhcount */ false, /* connoinherit */ false, /* conperiod */ + '\0', /* coninternaltype */ false); /* is_internal */ if (constrAddr) ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid); @@ -3714,6 +3715,7 @@ domainAddNotNullConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, 0, /* inhcount */ false, /* connoinherit */ false, /* conperiod */ + '\0', /* coninternaltype */ false); /* is_internal */ if (constrAddr) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 271ae26cbaf..0af53b6fbf3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -4222,11 +4222,10 @@ ConstraintElem: n->contype = CONSTR_NOTNULL; n->location = @1; n->keys = list_make1(makeString($3)); - /* no NOT VALID support yet */ processCASbits($4, @4, "NOT NULL", - NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &n->skip_validation, &n->is_no_inherit, yyscanner); - n->initially_valid = true; + n->initially_valid = !n->skip_validation; $$ = (Node *) n; } | UNIQUE opt_unique_null_treatment '(' columnList opt_without_overlaps ')' opt_c_include opt_definition OptConsTableSpace diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index abbe1bb45a3..d5fb9b70c9b 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1090,7 +1090,68 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("not-null constraints on partitioned tables cannot be NO INHERIT")); - cxt->nnconstraints = lappend(cxt->nnconstraints, constraint); + if (constraint->skip_validation && (strcmp(cxt->stmtType, "ALTER TABLE") == 0)) + { + Constraint *constr; + NullTest *nnulltest; + AttrNumber colnum; + TupleDesc tupdesc; + Form_pg_attribute attr; + + constr = makeNode(Constraint); + constr->contype = CONSTR_CHECK; + if (constraint->conname) + constr->conname = pstrdup(constraint->conname); + constr->deferrable = false; + constr->initdeferred = false; + constr->is_enforced = true; + constr->skip_validation = true; + constr->initially_valid = false; + constr->is_no_inherit = false; + constr->nulls_not_distinct = false; + constr->location = constraint->location; + constr->contype_internal = CONSTRAINT_NOTNULL; + + colnum = get_attnum(RelationGetRelid(cxt->rel), strVal(linitial(constraint->keys))); + if (colnum == InvalidAttrNumber) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + strVal(linitial(constraint->keys)), RelationGetRelationName(cxt->rel))); + if (colnum < InvalidAttrNumber) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot add not-null constraint on system column \"%s\"", + strVal(linitial(constraint->keys)))); + + tupdesc = RelationGetDescr(cxt->rel); + attr = TupleDescAttr(tupdesc, colnum - 1); + + nnulltest = makeNode(NullTest); + nnulltest->arg = (Expr *) makeVar(1, + attr->attnum, + attr->atttypid, + attr->atttypmod, + attr->attcollation, + 0); + + nnulltest->nulltesttype = IS_NOT_NULL; + nnulltest->argisrow = false; + nnulltest->location = constraint->location; + constr->cooked_expr = nodeToString(nnulltest); + + cxt->ckconstraints = lappend(cxt->ckconstraints, constr); + } + else if (constraint->skip_validation && (strcmp(cxt->stmtType, "CREATE TABLE") == 0)) + { + constraint->skip_validation = false; + constraint->initially_valid = true; + ereport(WARNING, + errmsg("CREATE TABLE NOT NULL NOT VALID CONSTRAINT WILL SET TO VALID")); + cxt->nnconstraints = lappend(cxt->nnconstraints, constraint); + } + else + cxt->nnconstraints = lappend(cxt->nnconstraints, constraint); break; case CONSTR_FOREIGN: diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index dbd339e9df4..70b61b12464 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -42,6 +42,7 @@ typedef struct CookedConstraint Node *expr; /* transformed default or check expr */ bool is_enforced; /* is enforced? (only for CHECK) */ bool skip_validation; /* skip validation? (only for CHECK) */ + char coninternaltype; /* (only for NOT NULL) */ bool is_local; /* constraint has local (non-inherited) def */ int16 inhcount; /* number of times constraint is inherited */ bool is_no_inherit; /* constraint has local def and cannot be diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 6da164e7e4d..5740a277d8f 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -114,6 +114,8 @@ CATALOG(pg_constraint,2606,ConstraintRelationId) */ bool conperiod; + char coninternaltype; /* constraint internal type; see codes below */ + #ifdef CATALOG_VARLEN /* variable-length fields start here */ /* @@ -250,6 +252,7 @@ extern Oid CreateConstraintEntry(const char *constraintName, int16 conInhCount, bool conNoInherit, bool conPeriod, + char coninternaltype, bool is_internal); extern bool ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 23c9e3c5abf..01a72589084 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2824,6 +2824,7 @@ typedef struct Constraint * untransformed parse tree */ char *cooked_expr; /* CHECK or DEFAULT expression, as * nodeToString representation */ + char contype_internal; /**/ char generated_when; /* ALWAYS or BY DEFAULT */ char generated_kind; /* STORED or VIRTUAL */ bool nulls_not_distinct; /* null treatment for UNIQUE constraints */ diff --git a/src/test/regress/expected/constraints.out b/src/test/regress/expected/constraints.out index 4f39100fcdf..cdba544bb8e 100644 --- a/src/test/regress/expected/constraints.out +++ b/src/test/regress/expected/constraints.out @@ -1392,3 +1392,66 @@ DROP TABLE constraint_comments_tbl; DROP DOMAIN constraint_comments_dom; DROP ROLE regress_constraint_comments; DROP ROLE regress_constraint_comments_noaccess; +-- test cases for not null not valid and validate it. +create table t0_ok(a int, constraint nn not null a not valid); +WARNING: CREATE TABLE NOT NULL NOT VALID CONSTRAINT WILL SET TO VALID +create table t_notnull_notvalid(a int); +insert into t_notnull_notvalid values(null); +alter table t_notnull_notvalid add constraint nc1 not null a not valid; +\d t_notnull_notvalid + Table "public.t_notnull_notvalid" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | +Check constraints: + "nc1" CHECK (a IS NOT NULL) NOT VALID + +alter table t_notnull_notvalid validate constraint nc1; --fail. +ERROR: column "a" of relation "t_notnull_notvalid" contains null values +truncate t_notnull_notvalid; +insert into t_notnull_notvalid values(1); +alter table t_notnull_notvalid validate constraint nc1; --ok +\d+ t_notnull_notvalid + Table "public.t_notnull_notvalid" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+---------+--------------+------------- + a | integer | | not null | | plain | | +Not-null constraints: + "nc1" NOT NULL "a" + +create table lp (a int) partition by list (a); +create table lp_default partition of lp default; +create table lp_ef partition of lp for values in (1,2,3,4); +create table lp_g partition of lp for values in (5,6,7,8); +create table lp_null partition of lp for values in (null); +alter table lp add constraint nc1 not null a not valid; +insert into lp select g from generate_Series(2, 9) g; +insert into lp_null select NULL; --fail +ERROR: new row for relation "lp_null" violates check constraint "nc1" +DETAIL: Failing row contains (null). +alter table lp_default validate constraint nc1; --fail +ERROR: cannot validate not-null constraint from partition "lp_default" +HINT: You might need validate not-null constraint through root partition "lp" +alter table lp_g validate constraint nc1; --fail +ERROR: cannot validate not-null constraint from partition "lp_g" +HINT: You might need validate not-null constraint through root partition "lp" +alter table lp validate constraint nc1; --ok. +alter table lp validate constraint nc1; --ok. +\d lp + Partitioned table "public.lp" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | not null | +Partition key: LIST (a) +Number of partitions: 4 (Use \d+ to list them.) + +\d lp_default + Table "public.lp_default" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | not null | +Partition of: lp DEFAULT + +drop table t0_ok; +drop table lp; +drop table t_notnull_notvalid; diff --git a/src/test/regress/sql/constraints.sql b/src/test/regress/sql/constraints.sql index 21ce4177de4..06c3895e745 100644 --- a/src/test/regress/sql/constraints.sql +++ b/src/test/regress/sql/constraints.sql @@ -836,3 +836,44 @@ DROP DOMAIN constraint_comments_dom; DROP ROLE regress_constraint_comments; DROP ROLE regress_constraint_comments_noaccess; + + +-- test cases for not null not valid and validate it. +create table t0_ok(a int, constraint nn not null a not valid); + +create table t_notnull_notvalid(a int); +insert into t_notnull_notvalid values(null); +alter table t_notnull_notvalid add constraint nc1 not null a not valid; +\d t_notnull_notvalid +alter table t_notnull_notvalid validate constraint nc1; --fail. + +truncate t_notnull_notvalid; +insert into t_notnull_notvalid values(1); +alter table t_notnull_notvalid validate constraint nc1; --ok +\d+ t_notnull_notvalid + + +create table lp (a int) partition by list (a); +create table lp_default partition of lp default; +create table lp_ef partition of lp for values in (1,2,3,4); +create table lp_g partition of lp for values in (5,6,7,8); +create table lp_null partition of lp for values in (null); +alter table lp add constraint nc1 not null a not valid; +insert into lp select g from generate_Series(2, 9) g; + +insert into lp_null select NULL; --fail +alter table lp_default validate constraint nc1; --fail +alter table lp_g validate constraint nc1; --fail + +alter table lp validate constraint nc1; --ok. +alter table lp validate constraint nc1; --ok. + +\d lp +\d lp_default + + + +drop table t0_ok; +drop table lp; +drop table t_notnull_notvalid; + -- 2.34.1