aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/execMain.c
diff options
context:
space:
mode:
authorPeter Eisentraut <peter@eisentraut.org>2025-03-28 13:53:37 +0100
committerPeter Eisentraut <peter@eisentraut.org>2025-03-28 13:53:37 +0100
commitcdc168ad4b22ea4183f966688b245cabb5935d1f (patch)
tree1755b8898eadbb54ceaee15acb612952f9bcaeb7 /src/backend/executor/execMain.c
parent747ddd38cbf6d32bca496e69c1efb2ae4fe333cc (diff)
downloadpostgresql-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.c220
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.
*