diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/heap.c | 10 | ||||
-rw-r--r-- | src/backend/commands/indexcmds.c | 10 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 71 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 220 | ||||
-rw-r--r-- | src/backend/parser/parse_utilcmd.c | 14 |
5 files changed, 233 insertions, 92 deletions
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index bd3554c0bfd..b807ab66668 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -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 diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 89cc83e8843..33c2106c17c 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -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")); } /* diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index afb25007613..10624353b0a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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 diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index a2271275571..2da848970be 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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,59 +2076,24 @@ 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); + 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); + } + } - /* - * 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); + /* + * Verify not-null constraints on virtual generated column, if any. + */ + if (notnull_virtual_attrs) + { + AttrNumber attnum; - 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)); - } - } + attnum = ExecRelGenVirtualNotNull(resultRelInfo, slot, estate, + notnull_virtual_attrs); + if (attnum != InvalidAttrNumber) + ReportNotNullViolationError(resultRelInfo, slot, estate, attnum); } /* @@ -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; @@ -2178,6 +2156,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. * diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 896a7f2c59b..9c1541e1fea 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -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))); } /* |