diff options
author | Peter Eisentraut <peter@eisentraut.org> | 2025-03-28 13:53:37 +0100 |
---|---|---|
committer | Peter Eisentraut <peter@eisentraut.org> | 2025-03-28 13:53:37 +0100 |
commit | cdc168ad4b22ea4183f966688b245cabb5935d1f (patch) | |
tree | 1755b8898eadbb54ceaee15acb612952f9bcaeb7 /src/backend/executor/execMain.c | |
parent | 747ddd38cbf6d32bca496e69c1efb2ae4fe333cc (diff) | |
download | postgresql-cdc168ad4b22ea4183f966688b245cabb5935d1f.tar.gz postgresql-cdc168ad4b22ea4183f966688b245cabb5935d1f.zip |
Add support for not-null constraints on virtual generated columns
This was left out of the original patch for virtual generated columns
(commit 83ea6c54025).
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
Diffstat (limited to 'src/backend/executor/execMain.c')
-rw-r--r-- | src/backend/executor/execMain.c | 220 |
1 files changed, 167 insertions, 53 deletions
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. * |