mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
Add support for not-null constraints on virtual generated columns
This was left out of the original patch for virtual generated columns
(commit 83ea6c5402
).
This just involves a bit of extra work in the executor to expand the
generation expressions and run a "IS NOT NULL" test against them.
There is also a bit of work to make sure that not-null constraints are
checked during a table rewrite.
Author: jian he <jian.universality@gmail.com>
Reviewed-by: Xuneng Zhou <xunengzhou@gmail.com>
Reviewed-by: Navneet Kumar <thanit3111@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@alvh.no-ip.org>
Discussion: https://postgr.es/m/CACJufxHArQysbDkWFmvK+D1TPHQWWTxWN15cMuUaTYX3xhQXgg@mail.gmail.com
This commit is contained in:
@ -2615,11 +2615,6 @@ AddRelationNewConstraints(Relation rel,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot add not-null constraint on system column \"%s\"",
|
||||
strVal(linitial(cdef->keys))));
|
||||
/* TODO: see transformColumnDefinition() */
|
||||
if (get_attgenerated(RelationGetRelid(rel), colnum) == ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("not-null constraints are not supported on virtual generated columns"));
|
||||
|
||||
/*
|
||||
* If the column already has a not-null constraint, we don't want
|
||||
@ -2935,11 +2930,6 @@ AddRelationNotNullConstraints(Relation rel, List *constraints,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot add not-null constraint on system column \"%s\"",
|
||||
strVal(linitial(constr->keys))));
|
||||
/* TODO: see transformColumnDefinition() */
|
||||
if (get_attgenerated(RelationGetRelid(rel), attnum) == ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("not-null constraints are not supported on virtual generated columns"));
|
||||
|
||||
/*
|
||||
* A column can only have one not-null constraint, so discard any
|
||||
|
@ -1118,10 +1118,12 @@ DefineIndex(Oid tableId,
|
||||
|
||||
if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
stmt->isconstraint ?
|
||||
errmsg("unique constraints on virtual generated columns are not supported") :
|
||||
errmsg("indexes on virtual generated columns are not supported")));
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
stmt->primary ?
|
||||
errmsg("primary keys on virtual generated columns are not supported") :
|
||||
stmt->isconstraint ?
|
||||
errmsg("unique constraints on virtual generated columns are not supported") :
|
||||
errmsg("indexes on virtual generated columns are not supported"));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -6101,6 +6101,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
||||
TupleDesc newTupDesc;
|
||||
bool needscan = false;
|
||||
List *notnull_attrs;
|
||||
List *notnull_virtual_attrs;
|
||||
int i;
|
||||
ListCell *l;
|
||||
EState *estate;
|
||||
@ -6185,22 +6186,32 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
||||
ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
|
||||
}
|
||||
|
||||
notnull_attrs = NIL;
|
||||
notnull_attrs = notnull_virtual_attrs = NIL;
|
||||
if (newrel || tab->verify_new_notnull)
|
||||
{
|
||||
/*
|
||||
* If we are rebuilding the tuples OR if we added any new but not
|
||||
* verified not-null constraints, check all not-null constraints. This
|
||||
* is a bit of overkill but it minimizes risk of bugs.
|
||||
*
|
||||
* notnull_attrs does *not* collect attribute numbers for not-null
|
||||
* constraints over virtual generated columns; instead, they are
|
||||
* collected in notnull_virtual_attrs.
|
||||
*/
|
||||
for (i = 0; i < newTupDesc->natts; i++)
|
||||
{
|
||||
Form_pg_attribute attr = TupleDescAttr(newTupDesc, i);
|
||||
|
||||
if (attr->attnotnull && !attr->attisdropped)
|
||||
notnull_attrs = lappend_int(notnull_attrs, attr->attnum);
|
||||
{
|
||||
if (attr->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
notnull_attrs = lappend_int(notnull_attrs, attr->attnum);
|
||||
else
|
||||
notnull_virtual_attrs = lappend_int(notnull_virtual_attrs,
|
||||
attr->attnum);
|
||||
}
|
||||
}
|
||||
if (notnull_attrs)
|
||||
if (notnull_attrs || notnull_virtual_attrs)
|
||||
needscan = true;
|
||||
}
|
||||
|
||||
@ -6214,6 +6225,29 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
||||
List *dropped_attrs = NIL;
|
||||
ListCell *lc;
|
||||
Snapshot snapshot;
|
||||
ResultRelInfo *rInfo = NULL;
|
||||
|
||||
/*
|
||||
* When adding or changing a virtual generated column with a not-null
|
||||
* constraint, we need to evaluate whether the generation expression
|
||||
* is null. For that, we borrow ExecRelGenVirtualNotNull(). Here, we
|
||||
* prepare a dummy ResultRelInfo.
|
||||
*/
|
||||
if (notnull_virtual_attrs != NIL)
|
||||
{
|
||||
MemoryContext oldcontext;
|
||||
|
||||
Assert(newTupDesc->constr->has_generated_virtual);
|
||||
Assert(newTupDesc->constr->has_not_null);
|
||||
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
rInfo = makeNode(ResultRelInfo);
|
||||
InitResultRelInfo(rInfo,
|
||||
oldrel,
|
||||
0, /* dummy rangetable index */
|
||||
NULL,
|
||||
estate->es_instrument);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
||||
if (newrel)
|
||||
ereport(DEBUG1,
|
||||
@ -6394,6 +6428,26 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
||||
}
|
||||
}
|
||||
|
||||
if (notnull_virtual_attrs != NIL)
|
||||
{
|
||||
AttrNumber attnum;
|
||||
|
||||
attnum = ExecRelGenVirtualNotNull(rInfo, insertslot,
|
||||
estate,
|
||||
notnull_virtual_attrs);
|
||||
if (attnum != InvalidAttrNumber)
|
||||
{
|
||||
Form_pg_attribute attr = TupleDescAttr(newTupDesc, attnum - 1);
|
||||
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_NOT_NULL_VIOLATION),
|
||||
errmsg("column \"%s\" of relation \"%s\" contains null values",
|
||||
NameStr(attr->attname),
|
||||
RelationGetRelationName(oldrel)),
|
||||
errtablecol(oldrel, attnum));
|
||||
}
|
||||
}
|
||||
|
||||
foreach(l, tab->constraints)
|
||||
{
|
||||
NewConstraint *con = lfirst(l);
|
||||
@ -7843,14 +7897,6 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
|
||||
errmsg("cannot alter system column \"%s\"",
|
||||
colName)));
|
||||
|
||||
/* TODO: see transformColumnDefinition() */
|
||||
if (TupleDescAttr(RelationGetDescr(rel), attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("not-null constraints are not supported on virtual generated columns"),
|
||||
errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
/* See if there's already a constraint */
|
||||
tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
|
||||
if (HeapTupleIsValid(tuple))
|
||||
@ -8519,6 +8565,9 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
|
||||
errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
|
||||
colName, RelationGetRelationName(rel))));
|
||||
|
||||
if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && attTup->attnotnull)
|
||||
tab->verify_new_notnull = true;
|
||||
|
||||
/*
|
||||
* We need to prevent this because a change of expression could affect a
|
||||
* row filter and inject expressions that are not permitted in a row
|
||||
|
@ -92,6 +92,9 @@ static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
|
||||
AclMode requiredPerms);
|
||||
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
|
||||
static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
|
||||
static void ReportNotNullViolationError(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *slot,
|
||||
EState *estate, int attnum);
|
||||
|
||||
/* end of local decls */
|
||||
|
||||
@ -1372,6 +1375,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
|
||||
resultRelInfo->ri_FdwState = NULL;
|
||||
resultRelInfo->ri_usesFdwDirectModify = false;
|
||||
resultRelInfo->ri_CheckConstraintExprs = NULL;
|
||||
resultRelInfo->ri_GenVirtualNotNullConstraintExprs = NULL;
|
||||
resultRelInfo->ri_GeneratedExprsI = NULL;
|
||||
resultRelInfo->ri_GeneratedExprsU = NULL;
|
||||
resultRelInfo->ri_projectReturning = NULL;
|
||||
@ -1842,7 +1846,7 @@ ExecutePlan(QueryDesc *queryDesc,
|
||||
|
||||
|
||||
/*
|
||||
* ExecRelCheck --- check that tuple meets constraints for result relation
|
||||
* ExecRelCheck --- check that tuple meets check constraints for result relation
|
||||
*
|
||||
* Returns NULL if OK, else name of failed check constraint
|
||||
*/
|
||||
@ -2056,11 +2060,15 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
TupleConstr *constr = tupdesc->constr;
|
||||
Bitmapset *modifiedCols;
|
||||
List *notnull_virtual_attrs = NIL;
|
||||
|
||||
Assert(constr); /* we should not be called otherwise */
|
||||
|
||||
/*
|
||||
* Verify not-null constraints.
|
||||
*
|
||||
* Not-null constraints on virtual generated columns are collected and
|
||||
* checked separately below.
|
||||
*/
|
||||
if (constr->has_not_null)
|
||||
{
|
||||
@ -2068,61 +2076,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
{
|
||||
Form_pg_attribute att = TupleDescAttr(tupdesc, attnum - 1);
|
||||
|
||||
if (att->attnotnull && slot_attisnull(slot, attnum))
|
||||
{
|
||||
char *val_desc;
|
||||
Relation orig_rel = rel;
|
||||
TupleDesc orig_tupdesc = RelationGetDescr(rel);
|
||||
|
||||
/*
|
||||
* If the tuple has been routed, it's been converted to the
|
||||
* partition's rowtype, which might differ from the root
|
||||
* table's. We must convert it back to the root table's
|
||||
* rowtype so that val_desc shown error message matches the
|
||||
* input tuple.
|
||||
*/
|
||||
if (resultRelInfo->ri_RootResultRelInfo)
|
||||
{
|
||||
ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo;
|
||||
AttrMap *map;
|
||||
|
||||
tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
|
||||
/* a reverse map */
|
||||
map = build_attrmap_by_name_if_req(orig_tupdesc,
|
||||
tupdesc,
|
||||
false);
|
||||
|
||||
/*
|
||||
* Partition-specific slot's tupdesc can't be changed, so
|
||||
* allocate a new one.
|
||||
*/
|
||||
if (map != NULL)
|
||||
slot = execute_attr_map_slot(map, slot,
|
||||
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
|
||||
modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate),
|
||||
ExecGetUpdatedCols(rootrel, estate));
|
||||
rel = rootrel->ri_RelationDesc;
|
||||
}
|
||||
else
|
||||
modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate),
|
||||
ExecGetUpdatedCols(resultRelInfo, estate));
|
||||
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
|
||||
slot,
|
||||
tupdesc,
|
||||
modifiedCols,
|
||||
64);
|
||||
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_NOT_NULL_VIOLATION),
|
||||
errmsg("null value in column \"%s\" of relation \"%s\" violates not-null constraint",
|
||||
NameStr(att->attname),
|
||||
RelationGetRelationName(orig_rel)),
|
||||
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
|
||||
errtablecol(orig_rel, attnum));
|
||||
}
|
||||
if (att->attnotnull && att->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
notnull_virtual_attrs = lappend_int(notnull_virtual_attrs, attnum);
|
||||
else if (att->attnotnull && slot_attisnull(slot, attnum))
|
||||
ReportNotNullViolationError(resultRelInfo, slot, estate, attnum);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify not-null constraints on virtual generated column, if any.
|
||||
*/
|
||||
if (notnull_virtual_attrs)
|
||||
{
|
||||
AttrNumber attnum;
|
||||
|
||||
attnum = ExecRelGenVirtualNotNull(resultRelInfo, slot, estate,
|
||||
notnull_virtual_attrs);
|
||||
if (attnum != InvalidAttrNumber)
|
||||
ReportNotNullViolationError(resultRelInfo, slot, estate, attnum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify check constraints.
|
||||
*/
|
||||
@ -2135,7 +2108,12 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
char *val_desc;
|
||||
Relation orig_rel = rel;
|
||||
|
||||
/* See the comment above. */
|
||||
/*
|
||||
* If the tuple has been routed, it's been converted to the
|
||||
* partition's rowtype, which might differ from the root table's.
|
||||
* We must convert it back to the root table's rowtype so that
|
||||
* val_desc shown error message matches the input tuple.
|
||||
*/
|
||||
if (resultRelInfo->ri_RootResultRelInfo)
|
||||
{
|
||||
ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo;
|
||||
@ -2177,6 +2155,142 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify not-null constraints on virtual generated columns of the given
|
||||
* tuple slot.
|
||||
*
|
||||
* Return value of InvalidAttrNumber means all not-null constraints on virtual
|
||||
* generated columns are satisfied. A return value > 0 means a not-null
|
||||
* violation happened for that attribute.
|
||||
*
|
||||
* notnull_virtual_attrs is the list of the attnums of virtual generated column with
|
||||
* not-null constraints.
|
||||
*/
|
||||
AttrNumber
|
||||
ExecRelGenVirtualNotNull(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
|
||||
EState *estate, List *notnull_virtual_attrs)
|
||||
{
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
ExprContext *econtext;
|
||||
MemoryContext oldContext;
|
||||
|
||||
/*
|
||||
* We implement this by building a NullTest node for each virtual
|
||||
* generated column, which we cache in resultRelInfo, and running those
|
||||
* through ExecCheck().
|
||||
*/
|
||||
if (resultRelInfo->ri_GenVirtualNotNullConstraintExprs == NULL)
|
||||
{
|
||||
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
resultRelInfo->ri_GenVirtualNotNullConstraintExprs =
|
||||
palloc0_array(ExprState *, list_length(notnull_virtual_attrs));
|
||||
|
||||
foreach_int(attnum, notnull_virtual_attrs)
|
||||
{
|
||||
int i = foreach_current_index(attnum);
|
||||
NullTest *nnulltest;
|
||||
|
||||
/* "generated_expression IS NOT NULL" check. */
|
||||
nnulltest = makeNode(NullTest);
|
||||
nnulltest->arg = (Expr *) build_generation_expression(rel, attnum);
|
||||
nnulltest->nulltesttype = IS_NOT_NULL;
|
||||
nnulltest->argisrow = false;
|
||||
nnulltest->location = -1;
|
||||
|
||||
resultRelInfo->ri_GenVirtualNotNullConstraintExprs[i] =
|
||||
ExecPrepareExpr((Expr *) nnulltest, estate);
|
||||
}
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* We will use the EState's per-tuple context for evaluating virtual
|
||||
* generated column not null constraint expressions (creating it if it's
|
||||
* not already there).
|
||||
*/
|
||||
econtext = GetPerTupleExprContext(estate);
|
||||
|
||||
/* Arrange for econtext's scan tuple to be the tuple under test */
|
||||
econtext->ecxt_scantuple = slot;
|
||||
|
||||
/* And evaluate the check constraints for virtual generated column */
|
||||
foreach_int(attnum, notnull_virtual_attrs)
|
||||
{
|
||||
int i = foreach_current_index(attnum);
|
||||
ExprState *exprstate = resultRelInfo->ri_GenVirtualNotNullConstraintExprs[i];
|
||||
|
||||
Assert(exprstate != NULL);
|
||||
if (!ExecCheck(exprstate, econtext))
|
||||
return attnum;
|
||||
}
|
||||
|
||||
/* InvalidAttrNumber result means no error */
|
||||
return InvalidAttrNumber;
|
||||
}
|
||||
|
||||
/*
|
||||
* Report a violation of a not-null constraint that was already detected.
|
||||
*/
|
||||
static void
|
||||
ReportNotNullViolationError(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
|
||||
EState *estate, int attnum)
|
||||
{
|
||||
Bitmapset *modifiedCols;
|
||||
char *val_desc;
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
Relation orig_rel = rel;
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
TupleDesc orig_tupdesc = RelationGetDescr(rel);
|
||||
Form_pg_attribute att = TupleDescAttr(tupdesc, attnum - 1);
|
||||
|
||||
Assert(attnum > 0);
|
||||
|
||||
/*
|
||||
* If the tuple has been routed, it's been converted to the partition's
|
||||
* rowtype, which might differ from the root table's. We must convert it
|
||||
* back to the root table's rowtype so that val_desc shown error message
|
||||
* matches the input tuple.
|
||||
*/
|
||||
if (resultRelInfo->ri_RootResultRelInfo)
|
||||
{
|
||||
ResultRelInfo *rootrel = resultRelInfo->ri_RootResultRelInfo;
|
||||
AttrMap *map;
|
||||
|
||||
tupdesc = RelationGetDescr(rootrel->ri_RelationDesc);
|
||||
/* a reverse map */
|
||||
map = build_attrmap_by_name_if_req(orig_tupdesc,
|
||||
tupdesc,
|
||||
false);
|
||||
|
||||
/*
|
||||
* Partition-specific slot's tupdesc can't be changed, so allocate a
|
||||
* new one.
|
||||
*/
|
||||
if (map != NULL)
|
||||
slot = execute_attr_map_slot(map, slot,
|
||||
MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
|
||||
modifiedCols = bms_union(ExecGetInsertedCols(rootrel, estate),
|
||||
ExecGetUpdatedCols(rootrel, estate));
|
||||
rel = rootrel->ri_RelationDesc;
|
||||
}
|
||||
else
|
||||
modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate),
|
||||
ExecGetUpdatedCols(resultRelInfo, estate));
|
||||
|
||||
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
|
||||
slot,
|
||||
tupdesc,
|
||||
modifiedCols,
|
||||
64);
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_NOT_NULL_VIOLATION),
|
||||
errmsg("null value in column \"%s\" of relation \"%s\" violates not-null constraint",
|
||||
NameStr(att->attname),
|
||||
RelationGetRelationName(orig_rel)),
|
||||
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
|
||||
errtablecol(orig_rel, attnum));
|
||||
}
|
||||
|
||||
/*
|
||||
* ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs
|
||||
* of the specified kind.
|
||||
|
@ -988,20 +988,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
|
||||
column->colname, cxt->relation->relname),
|
||||
parser_errposition(cxt->pstate,
|
||||
constraint->location)));
|
||||
|
||||
/*
|
||||
* TODO: Straightforward not-null constraints won't work on virtual
|
||||
* generated columns, because there is no support for expanding the
|
||||
* column when the constraint is checked. Maybe we could convert the
|
||||
* not-null constraint into a full check constraint, so that the
|
||||
* generation expression can be expanded at check time.
|
||||
*/
|
||||
if (column->is_not_null && column->generated == ATTRIBUTE_GENERATED_VIRTUAL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("not-null constraints are not supported on virtual generated columns"),
|
||||
parser_errposition(cxt->pstate,
|
||||
constraint->location)));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -257,6 +257,10 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid,
|
||||
extern List *ExecGetAncestorResultRels(EState *estate, ResultRelInfo *resultRelInfo);
|
||||
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *slot, EState *estate);
|
||||
extern AttrNumber ExecRelGenVirtualNotNull(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *slot,
|
||||
EState *estate,
|
||||
List *notnull_virtual_attrs);
|
||||
extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *slot, EState *estate, bool emitError);
|
||||
extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
|
||||
|
@ -549,6 +549,12 @@ typedef struct ResultRelInfo
|
||||
/* array of expr states for checking check constraints */
|
||||
ExprState **ri_CheckConstraintExprs;
|
||||
|
||||
/*
|
||||
* array of expr states for checking not-null constraints on virtual
|
||||
* generated columns
|
||||
*/
|
||||
ExprState **ri_GenVirtualNotNullConstraintExprs;
|
||||
|
||||
/*
|
||||
* Arrays of stored generated columns ExprStates for INSERT/UPDATE/MERGE.
|
||||
*/
|
||||
|
@ -664,28 +664,73 @@ INSERT INTO gtest20c VALUES (1); -- ok
|
||||
INSERT INTO gtest20c VALUES (NULL); -- fails
|
||||
ERROR: new row for relation "gtest20c" violates check constraint "whole_row_check"
|
||||
DETAIL: Failing row contains (null, virtual).
|
||||
-- not-null constraints (currently not supported)
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL);
|
||||
ERROR: not-null constraints are not supported on virtual generated columns
|
||||
LINE 1: ... b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL);
|
||||
^
|
||||
--INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
--INSERT INTO gtest21a (a) VALUES (0); -- violates constraint
|
||||
INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21a (a) VALUES (0); -- violates constraint
|
||||
ERROR: null value in column "b" of relation "gtest21a" violates not-null constraint
|
||||
DETAIL: Failing row contains (0, virtual).
|
||||
-- also check with table constraint syntax
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL, CONSTRAINT cc NOT NULL b); -- error
|
||||
ERROR: not-null constraints are not supported on virtual generated columns
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
ALTER TABLE gtest21ax ADD CONSTRAINT cc NOT NULL b; -- error
|
||||
ERROR: not-null constraints are not supported on virtual generated columns
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL, CONSTRAINT cc NOT NULL b);
|
||||
INSERT INTO gtest21ax (a) VALUES (0); -- violates constraint
|
||||
ERROR: null value in column "b" of relation "gtest21ax" violates not-null constraint
|
||||
DETAIL: Failing row contains (0, virtual).
|
||||
INSERT INTO gtest21ax (a) VALUES (1); --ok
|
||||
-- SET EXPRESSION supports not null constraint
|
||||
ALTER TABLE gtest21ax ALTER COLUMN b SET EXPRESSION AS (nullif(a, 1)); --error
|
||||
ERROR: column "b" of relation "gtest21ax" contains null values
|
||||
DROP TABLE gtest21ax;
|
||||
CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
ALTER TABLE gtest21ax ADD CONSTRAINT cc NOT NULL b;
|
||||
INSERT INTO gtest21ax (a) VALUES (0); -- violates constraint
|
||||
ERROR: null value in column "b" of relation "gtest21ax" violates not-null constraint
|
||||
DETAIL: Failing row contains (0, virtual).
|
||||
DROP TABLE gtest21ax;
|
||||
CREATE TABLE gtest21b (a int, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL;
|
||||
ERROR: not-null constraints are not supported on virtual generated columns
|
||||
DETAIL: Column "b" of relation "gtest21b" is a virtual generated column.
|
||||
--INSERT INTO gtest21b (a) VALUES (1); -- ok
|
||||
--INSERT INTO gtest21b (a) VALUES (0); -- violates constraint
|
||||
INSERT INTO gtest21b (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21b (a) VALUES (2), (0); -- violates constraint
|
||||
ERROR: null value in column "b" of relation "gtest21b" violates not-null constraint
|
||||
DETAIL: Failing row contains (0, virtual).
|
||||
INSERT INTO gtest21b (a) VALUES (NULL); -- error
|
||||
ERROR: null value in column "b" of relation "gtest21b" violates not-null constraint
|
||||
DETAIL: Failing row contains (null, virtual).
|
||||
ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL;
|
||||
--INSERT INTO gtest21b (a) VALUES (0); -- ok now
|
||||
INSERT INTO gtest21b (a) VALUES (0); -- ok now
|
||||
-- not-null constraint with partitioned table
|
||||
CREATE TABLE gtestnn_parent (
|
||||
f1 int,
|
||||
f2 bigint,
|
||||
f3 bigint GENERATED ALWAYS AS (nullif(f1, 1) + nullif(f2, 10)) VIRTUAL NOT NULL
|
||||
) PARTITION BY RANGE (f1);
|
||||
CREATE TABLE gtestnn_child PARTITION OF gtestnn_parent FOR VALUES FROM (1) TO (5);
|
||||
CREATE TABLE gtestnn_childdef PARTITION OF gtestnn_parent default;
|
||||
-- check the error messages
|
||||
INSERT INTO gtestnn_parent VALUES (2, 2, default), (3, 5, default), (14, 12, default); -- ok
|
||||
INSERT INTO gtestnn_parent VALUES (1, 2, default); -- error
|
||||
ERROR: null value in column "f3" of relation "gtestnn_child" violates not-null constraint
|
||||
DETAIL: Failing row contains (1, 2, virtual).
|
||||
INSERT INTO gtestnn_parent VALUES (2, 10, default); -- error
|
||||
ERROR: null value in column "f3" of relation "gtestnn_child" violates not-null constraint
|
||||
DETAIL: Failing row contains (2, 10, virtual).
|
||||
ALTER TABLE gtestnn_parent ALTER COLUMN f3 SET EXPRESSION AS (nullif(f1, 2) + nullif(f2, 11)); -- error
|
||||
ERROR: column "f3" of relation "gtestnn_child" contains null values
|
||||
INSERT INTO gtestnn_parent VALUES (10, 11, default); -- ok
|
||||
SELECT * FROM gtestnn_parent ORDER BY f1, f2, f3;
|
||||
f1 | f2 | f3
|
||||
----+----+----
|
||||
2 | 2 | 4
|
||||
3 | 5 | 8
|
||||
10 | 11 | 21
|
||||
14 | 12 | 26
|
||||
(4 rows)
|
||||
|
||||
-- test ALTER TABLE ADD COLUMN
|
||||
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 14) + nullif(f2, 10)) VIRTUAL; -- error
|
||||
ERROR: column "c" of relation "gtestnn_childdef" contains null values
|
||||
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 13) + nullif(f2, 5)) VIRTUAL; -- error
|
||||
ERROR: column "c" of relation "gtestnn_child" contains null values
|
||||
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 4) + nullif(f2, 6)) VIRTUAL; -- ok
|
||||
-- index constraints
|
||||
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE);
|
||||
ERROR: unique constraints on virtual generated columns are not supported
|
||||
@ -693,7 +738,7 @@ ERROR: unique constraints on virtual generated columns are not supported
|
||||
--INSERT INTO gtest22a VALUES (3);
|
||||
--INSERT INTO gtest22a VALUES (4);
|
||||
CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY KEY (a, b));
|
||||
ERROR: not-null constraints are not supported on virtual generated columns
|
||||
ERROR: primary keys on virtual generated columns are not supported
|
||||
--INSERT INTO gtest22b VALUES (2);
|
||||
--INSERT INTO gtest22b VALUES (2);
|
||||
-- indexes
|
||||
@ -738,7 +783,7 @@ ERROR: foreign key constraints on virtual generated columns are not supported
|
||||
--DROP TABLE gtest23b;
|
||||
--DROP TABLE gtest23a;
|
||||
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) VIRTUAL, PRIMARY KEY (y));
|
||||
ERROR: not-null constraints are not supported on virtual generated columns
|
||||
ERROR: primary keys on virtual generated columns are not supported
|
||||
--INSERT INTO gtest23p VALUES (1), (2), (3);
|
||||
CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
|
||||
ERROR: relation "gtest23p" does not exist
|
||||
@ -1056,6 +1101,15 @@ LINE 1: ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0...
|
||||
DETAIL: Column "x" is a generated column.
|
||||
ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error
|
||||
ERROR: column "x" of relation "gtest27" is a generated column
|
||||
-- test not-null checking during table rewrite
|
||||
INSERT INTO gtest27 (a, b) VALUES (NULL, NULL);
|
||||
ALTER TABLE gtest27
|
||||
DROP COLUMN x,
|
||||
ALTER COLUMN a TYPE bigint,
|
||||
ALTER COLUMN b TYPE bigint,
|
||||
ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) VIRTUAL NOT NULL; -- error
|
||||
ERROR: column "x" of relation "gtest27" contains null values
|
||||
DELETE FROM gtest27 WHERE a IS NULL AND b IS NULL;
|
||||
-- It's possible to alter the column types this way:
|
||||
ALTER TABLE gtest27
|
||||
DROP COLUMN x,
|
||||
|
@ -335,23 +335,51 @@ ALTER TABLE gtest20c ADD CONSTRAINT whole_row_check CHECK (gtest20c IS NOT NULL)
|
||||
INSERT INTO gtest20c VALUES (1); -- ok
|
||||
INSERT INTO gtest20c VALUES (NULL); -- fails
|
||||
|
||||
-- not-null constraints (currently not supported)
|
||||
-- not-null constraints
|
||||
CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL);
|
||||
--INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
--INSERT INTO gtest21a (a) VALUES (0); -- violates constraint
|
||||
INSERT INTO gtest21a (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21a (a) VALUES (0); -- violates constraint
|
||||
|
||||
-- also check with table constraint syntax
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL, CONSTRAINT cc NOT NULL b); -- error
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
ALTER TABLE gtest21ax ADD CONSTRAINT cc NOT NULL b; -- error
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL, CONSTRAINT cc NOT NULL b);
|
||||
INSERT INTO gtest21ax (a) VALUES (0); -- violates constraint
|
||||
INSERT INTO gtest21ax (a) VALUES (1); --ok
|
||||
-- SET EXPRESSION supports not null constraint
|
||||
ALTER TABLE gtest21ax ALTER COLUMN b SET EXPRESSION AS (nullif(a, 1)); --error
|
||||
DROP TABLE gtest21ax;
|
||||
|
||||
CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
CREATE TABLE gtest21ax (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
ALTER TABLE gtest21ax ADD CONSTRAINT cc NOT NULL b;
|
||||
INSERT INTO gtest21ax (a) VALUES (0); -- violates constraint
|
||||
DROP TABLE gtest21ax;
|
||||
|
||||
CREATE TABLE gtest21b (a int, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL);
|
||||
ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL;
|
||||
--INSERT INTO gtest21b (a) VALUES (1); -- ok
|
||||
--INSERT INTO gtest21b (a) VALUES (0); -- violates constraint
|
||||
INSERT INTO gtest21b (a) VALUES (1); -- ok
|
||||
INSERT INTO gtest21b (a) VALUES (2), (0); -- violates constraint
|
||||
INSERT INTO gtest21b (a) VALUES (NULL); -- error
|
||||
ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL;
|
||||
--INSERT INTO gtest21b (a) VALUES (0); -- ok now
|
||||
INSERT INTO gtest21b (a) VALUES (0); -- ok now
|
||||
|
||||
-- not-null constraint with partitioned table
|
||||
CREATE TABLE gtestnn_parent (
|
||||
f1 int,
|
||||
f2 bigint,
|
||||
f3 bigint GENERATED ALWAYS AS (nullif(f1, 1) + nullif(f2, 10)) VIRTUAL NOT NULL
|
||||
) PARTITION BY RANGE (f1);
|
||||
CREATE TABLE gtestnn_child PARTITION OF gtestnn_parent FOR VALUES FROM (1) TO (5);
|
||||
CREATE TABLE gtestnn_childdef PARTITION OF gtestnn_parent default;
|
||||
-- check the error messages
|
||||
INSERT INTO gtestnn_parent VALUES (2, 2, default), (3, 5, default), (14, 12, default); -- ok
|
||||
INSERT INTO gtestnn_parent VALUES (1, 2, default); -- error
|
||||
INSERT INTO gtestnn_parent VALUES (2, 10, default); -- error
|
||||
ALTER TABLE gtestnn_parent ALTER COLUMN f3 SET EXPRESSION AS (nullif(f1, 2) + nullif(f2, 11)); -- error
|
||||
INSERT INTO gtestnn_parent VALUES (10, 11, default); -- ok
|
||||
SELECT * FROM gtestnn_parent ORDER BY f1, f2, f3;
|
||||
-- test ALTER TABLE ADD COLUMN
|
||||
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 14) + nullif(f2, 10)) VIRTUAL; -- error
|
||||
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 13) + nullif(f2, 5)) VIRTUAL; -- error
|
||||
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 4) + nullif(f2, 6)) VIRTUAL; -- ok
|
||||
|
||||
-- index constraints
|
||||
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE);
|
||||
@ -531,6 +559,14 @@ ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric;
|
||||
SELECT * FROM gtest27;
|
||||
ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error
|
||||
ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error
|
||||
-- test not-null checking during table rewrite
|
||||
INSERT INTO gtest27 (a, b) VALUES (NULL, NULL);
|
||||
ALTER TABLE gtest27
|
||||
DROP COLUMN x,
|
||||
ALTER COLUMN a TYPE bigint,
|
||||
ALTER COLUMN b TYPE bigint,
|
||||
ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) VIRTUAL NOT NULL; -- error
|
||||
DELETE FROM gtest27 WHERE a IS NULL AND b IS NULL;
|
||||
-- It's possible to alter the column types this way:
|
||||
ALTER TABLE gtest27
|
||||
DROP COLUMN x,
|
||||
|
Reference in New Issue
Block a user