On Mon, Apr 28, 2025 at 10:45 AM jian he <jian.universal...@gmail.com> wrote:
>
> summary of attached patch:
> v1-0001
> we need to compute the generation expression for the domain with constraints,
> thus rename ExecComputeStoredGenerated to ExecComputeGenerated.
>
> v1-0002
> soft error variant of ExecPrepareExpr, ExecInitExpr.  for soft error 
> processing
> of CoerceToDomain.  we don't want error messages like "value for domain d2
> violates check constraint "d2_check"" while validating existing domain data, 
> we
> want something like:
> ERROR: column "b" of table "gtest24" contains values that violate the
> new constraint
>
> v1-0003 virtual generation columns over domain.

hi.

new patches attached.
mainly code refactoring, also try to reduce some unnecessary regress tests.
From ffd307605c1cfadd5a28090f90d54dceb75e6bc1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universal...@gmail.com>
Date: Wed, 21 May 2025 10:59:07 +0800
Subject: [PATCH v2 1/3] rename ExecComputeStoredGenerated to
 ExecComputeGenerated

to support virtual generated column over domain type, we actually need compute
the virtual generated expression. we did it at ExecComputeGenerated for stored,
we can use it for virtual.  so do the rename.

discussion: https://postgr.es/m/CACJufxFNUHxuSE=g20c2alo3d+4t_j9h0x8esmirzcc4frl...@mail.gmail.com
---
 src/backend/commands/copyfrom.c        |  4 ++--
 src/backend/executor/execReplication.c |  8 ++++----
 src/backend/executor/nodeModifyTable.c | 20 ++++++++++----------
 src/include/executor/nodeModifyTable.h |  5 ++---
 4 files changed, 18 insertions(+), 19 deletions(-)

diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index fbbbc09a97b..906b6581e11 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1346,8 +1346,8 @@ CopyFrom(CopyFromState cstate)
 				/* Compute stored generated columns */
 				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
 					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
-					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
-											   CMD_INSERT);
+					ExecComputeGenerated(resultRelInfo, estate, myslot,
+										 CMD_INSERT);
 
 				/*
 				 * If the target is a plain table, check the constraints of
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 53ddd25c42d..ca300ac0f00 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -587,8 +587,8 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
 			rel->rd_att->constr->has_generated_stored)
-			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
-									   CMD_INSERT);
+			ExecComputeGenerated(resultRelInfo, estate, slot,
+								 CMD_INSERT);
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
@@ -684,8 +684,8 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
 			rel->rd_att->constr->has_generated_stored)
-			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
-									   CMD_UPDATE);
+			ExecComputeGenerated(resultRelInfo, estate, slot,
+								 CMD_UPDATE);
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 46d533b7288..ab41ad8a8bc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -537,12 +537,12 @@ ExecInitGenerated(ResultRelInfo *resultRelInfo,
 }
 
 /*
- * Compute stored generated columns for a tuple
+ * Compute generated columns for a tuple.
+ * we might support virtual generated column in future, currently not.
  */
 void
-ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
-						   EState *estate, TupleTableSlot *slot,
-						   CmdType cmdtype)
+ExecComputeGenerated(ResultRelInfo *resultRelInfo, EState *estate,
+					 TupleTableSlot *slot, CmdType cmdtype)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -931,8 +931,8 @@ ExecInsert(ModifyTableContext *context,
 		 */
 		if (resultRelationDesc->rd_att->constr &&
 			resultRelationDesc->rd_att->constr->has_generated_stored)
-			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
-									   CMD_INSERT);
+			ExecComputeGenerated(resultRelInfo, estate, slot,
+								 CMD_INSERT);
 
 		/*
 		 * If the FDW supports batching, and batching is requested, accumulate
@@ -1058,8 +1058,8 @@ ExecInsert(ModifyTableContext *context,
 		 */
 		if (resultRelationDesc->rd_att->constr &&
 			resultRelationDesc->rd_att->constr->has_generated_stored)
-			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
-									   CMD_INSERT);
+			ExecComputeGenerated(resultRelInfo, estate, slot,
+								 CMD_INSERT);
 
 		/*
 		 * Check any RLS WITH CHECK policies.
@@ -2146,8 +2146,8 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelationDesc->rd_att->constr &&
 		resultRelationDesc->rd_att->constr->has_generated_stored)
-		ExecComputeStoredGenerated(resultRelInfo, estate, slot,
-								   CMD_UPDATE);
+		ExecComputeGenerated(resultRelInfo, estate, slot,
+							 CMD_UPDATE);
 }
 
 /*
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index bf3b592e28f..de374c46d3c 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -19,9 +19,8 @@ extern void ExecInitGenerated(ResultRelInfo *resultRelInfo,
 							  EState *estate,
 							  CmdType cmdtype);
 
-extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
-									   EState *estate, TupleTableSlot *slot,
-									   CmdType cmdtype);
+extern void ExecComputeGenerated(ResultRelInfo *resultRelInfo, EState *estate,
+								 TupleTableSlot *slot,CmdType cmdtype);
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
-- 
2.34.1

From 3a870d869ae01643f01eab471593c1ec4b819cfd Mon Sep 17 00:00:00 2001
From: jian he <jian.universal...@gmail.com>
Date: Wed, 21 May 2025 09:59:52 +0800
Subject: [PATCH v2 3/3] domain over virtual generated column

domains that don't have constraints work just fine.  domain constraints check
happen within ExecComputeGenerated.  we compute the virtual generated column in
ExecComputeGenerated and discarded the value.  if value cannot coerce to domain
then we will error out.

domain_with_constaint can be element type of range, multirange, array,
composite. To be bulletproof, if this column type is not type of TYPTYPE_BASE or
it's an array type, then we do actually compute the virtual generated
expression.  This can have negative performance for some INSERTs.  The following
two query shows in pg_catalog, most of the system type is TYPTYPE_BASE.  So I
guess this compromise is fine.

select count(*),typtype from pg_type
where typnamespace = 'pg_catalog'::regnamespace
group by 2 order by 1 desc;

ALTER DOMAIN ADD CONSTRAINT is supported for virtual generated column.

ALTER TABLE ADD VIRTUAL GENERATED COLUMN.
need table scan to verify new column generation expression value
satisfied the domain constraints. but no need table rewrite.

discussion: https://postgr.es/m/CACJufxFNUHxuSE=g20c2alo3d+4t_j9h0x8esmirzcc4frl...@mail.gmail.com
---
 src/backend/catalog/heap.c                    |  11 -
 src/backend/commands/copyfrom.c               |   5 +-
 src/backend/commands/tablecmds.c              |  49 +++-
 src/backend/commands/typecmds.c               | 248 ++++++++++++++++--
 src/backend/executor/nodeModifyTable.c        |  84 ++++--
 src/test/regress/expected/fast_default.out    |   9 +
 .../regress/expected/generated_virtual.out    |  72 ++++-
 src/test/regress/sql/fast_default.sql         |   9 +
 src/test/regress/sql/generated_virtual.sql    |  50 +++-
 9 files changed, 459 insertions(+), 78 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index fbaed5359ad..9f117b88999 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -583,17 +583,6 @@ CheckAttributeType(const char *attname,
 	}
 	else if (att_typtype == TYPTYPE_DOMAIN)
 	{
-		/*
-		 * Prevent virtual generated columns from having a domain type.  We
-		 * would have to enforce domain constraints when columns underlying
-		 * the generated column change.  This could possibly be implemented,
-		 * but it's not.
-		 */
-		if (flags & CHKATYPE_IS_VIRTUAL)
-			ereport(ERROR,
-					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					errmsg("virtual generated column \"%s\" cannot have a domain type", attname));
-
 		/*
 		 * If it's a domain, recurse to check its base type.
 		 */
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 906b6581e11..0086e4b2936 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1343,9 +1343,10 @@ CopyFrom(CopyFromState cstate)
 			}
 			else
 			{
-				/* Compute stored generated columns */
+				/* Compute generated columns */
 				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
-					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
+					(resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored ||
+					 resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_virtual))
 					ExecComputeGenerated(resultRelInfo, estate, myslot,
 										 CMD_INSERT);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 54ad38247aa..542b109b330 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6009,7 +6009,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 			 * rebuild data.
 			 */
 			if (tab->constraints != NIL || tab->verify_new_notnull ||
-				tab->partition_constraint != NULL)
+				tab->newvals || tab->partition_constraint != NULL)
 				ATRewriteTable(tab, InvalidOid);
 
 			/*
@@ -6129,6 +6129,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	List		*domain_virtual_attrs = NIL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -6204,6 +6205,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 
 		/* expr already planned */
 		ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
+		if (tab->rewrite == 0 && ex->is_generated)
+		{
+			Form_pg_attribute attr = TupleDescAttr(newTupDesc, ex->attnum - 1);
+			if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
+				DomainHasConstraints(attr->atttypid))
+			{
+				Assert(!attr->attisdropped);
+				domain_virtual_attrs = lappend_int(domain_virtual_attrs, attr->attnum);
+			}
+		}
 	}
 
 	notnull_attrs = notnull_virtual_attrs = NIL;
@@ -6239,6 +6250,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 			needscan = true;
 	}
 
+	/*
+	 * We need verify domain constraints on the virtual generated column are
+	 * satisfied, which requires table scan.
+	*/
+	if (domain_virtual_attrs != NIL)
+		needscan = true;
+
 	if (newrel || needscan)
 	{
 		ExprContext *econtext;
@@ -6497,6 +6515,35 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 				}
 			}
 
+			/*
+			 * domain_virtual_attrs is a list of newly added columns that are
+			 * virtual generated columns with a domain data type and associated
+			 * constraints. We need to verify that these domain constraints are
+			 * satisfied.
+			*/
+			if (domain_virtual_attrs != NIL)
+			{
+				Assert(tab->rewrite == 0);
+
+				foreach(l, tab->newvals)
+				{
+					NewColumnValue *ex = lfirst(l);
+
+					if (!ex->is_generated)
+						continue;
+
+					if(list_member_int(domain_virtual_attrs, ex->attnum) &&
+					   !ExecCheck(ex->exprstate, econtext))
+					{
+						Form_pg_attribute attr = TupleDescAttr(newTupDesc, ex->attnum - 1);
+						ereport(ERROR,
+								errcode(ERRCODE_CHECK_VIOLATION),
+								errmsg("value for domain %s is violated by some row",
+								format_type_be(attr->atttypid)));
+					}
+				}
+			}
+
 			if (partqualstate && !ExecCheck(partqualstate, econtext))
 			{
 				if (tab->validate_default)
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 45ae7472ab5..b6f28d41e8d 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -65,6 +65,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
@@ -127,6 +128,7 @@ static Oid	findRangeSubOpclass(List *opcname, Oid subtype);
 static Oid	findRangeCanonicalFunction(List *procname, Oid typeOid);
 static Oid	findRangeSubtypeDiffFunction(List *procname, Oid subtype);
 static void validateDomainCheckConstraint(Oid domainoid, const char *ccbin);
+static void DomainNotValidConstrOnVirtual(Oid domainoid, const char *domainName);
 static void validateDomainNotNullConstraint(Oid domainoid);
 static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
 static void checkEnumOwner(HeapTuple tup);
@@ -142,6 +144,7 @@ static void domainAddNotNullConstraint(Oid domainOid, Oid domainNamespace, Oid b
 static void AlterTypeRecurse(Oid typeOid, bool isImplicitArray,
 							 HeapTuple tup, Relation catalog,
 							 AlterTypeRecurseParams *atparams);
+static bool HasDomainOverVirtualGenerated(RelToCheck *rtc);
 
 
 /*
@@ -2978,7 +2981,17 @@ AlterDomainAddConstraint(List *names, Node *newConstraint,
 		 * to.
 		 */
 		if (!constr->skip_validation)
+		{
+			/*
+			 * we need updated domain constraint catalog info for validate
+			 * domain check constraints.
+			 */
+			CommandCounterIncrement();
+
 			validateDomainCheckConstraint(domainoid, ccbin);
+		}
+		else
+			DomainNotValidConstrOnVirtual(domainoid, NameStr(typTup->typname));
 
 		/*
 		 * We must send out an sinval message for the domain, to ensure that
@@ -3001,7 +3014,15 @@ AlterDomainAddConstraint(List *names, Node *newConstraint,
 								   constr, NameStr(typTup->typname), constrAddr);
 
 		if (!constr->skip_validation)
+		{
+			/*
+			 * we need updated domain constraint catalog info for validate
+			 * domain not-null constraints.
+			*/
+			CommandCounterIncrement();
+
 			validateDomainNotNullConstraint(domainoid);
+		}
 
 		typTup->typnotnull = true;
 		CatalogTupleUpdate(typrel, &tup->t_self, tup);
@@ -3116,6 +3137,30 @@ AlterDomainValidateConstraint(List *names, const char *constrName)
 	return address;
 }
 
+/*
+ * HasDomainOverVirtualGenerated
+ * return true if any domain column is virtual generated column
+*/
+static bool
+HasDomainOverVirtualGenerated(RelToCheck *rtc)
+{
+	Relation	testrel = rtc->rel;
+	TupleDesc	tupdesc = RelationGetDescr(testrel);
+
+	if (tupdesc->constr && tupdesc->constr->has_generated_virtual)
+	{
+		for (int i = 0; i < rtc->natts; i++)
+		{
+			int			attnum = rtc->atts[i];
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+			if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+				return true;
+		}
+	}
+	return false;
+}
+
 /*
  * Verify that all columns currently using the domain are not null.
  */
@@ -3124,6 +3169,11 @@ validateDomainNotNullConstraint(Oid domainoid)
 {
 	List	   *rels;
 	ListCell   *rt;
+	EState	   *estate;
+	ExprContext *econtext;
+
+	estate = CreateExecutorState();
+	econtext = GetPerTupleExprContext(estate);
 
 	/* Fetch relation list with attributes based on this domain */
 	/* ShareLock is sufficient to prevent concurrent data changes */
@@ -3138,6 +3188,37 @@ validateDomainNotNullConstraint(Oid domainoid)
 		TupleTableSlot *slot;
 		TableScanDesc scan;
 		Snapshot	snapshot;
+		ExprState **ri_GeneratedExprs = NULL;
+
+		/* cacahe virtual generated columns handling */
+		if (HasDomainOverVirtualGenerated(rtc))
+		{
+			ri_GeneratedExprs = (ExprState **) palloc0(rtc->natts * sizeof(ExprState *));
+
+			/*
+			 * We implement this by building a NullTest node for each virtual
+			 * generated column, which we stored it in ri_GeneratedExprs, and
+			 * running those through ExecCheck().
+			*/
+			for (int i = 0; i < rtc->natts; i++)
+			{
+				int			attnum = rtc->atts[i];
+				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+				if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+				{
+					NullTest   *nnulltest;
+
+					nnulltest = makeNode(NullTest);
+					nnulltest->arg = (Expr *) build_generation_expression(testrel, attnum);
+					nnulltest->nulltesttype = IS_NOT_NULL;
+					nnulltest->argisrow = false;
+					nnulltest->location = -1;
+
+					ri_GeneratedExprs[i] = ExecPrepareExpr((Expr *) nnulltest, estate);
+				}
+			}
+		}
 
 		/* Scan all tuples in this relation */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
@@ -3153,7 +3234,7 @@ validateDomainNotNullConstraint(Oid domainoid)
 				int			attnum = rtc->atts[i];
 				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
 
-				if (slot_attisnull(slot, attnum))
+				if (attr->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL && slot_attisnull(slot, attnum))
 				{
 					/*
 					 * In principle the auxiliary information for this error
@@ -3169,7 +3250,22 @@ validateDomainNotNullConstraint(Oid domainoid)
 									RelationGetRelationName(testrel)),
 							 errtablecol(testrel, attnum)));
 				}
+
+				if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+				{
+					econtext->ecxt_scantuple = slot;
+					Assert(ri_GeneratedExprs[i] != NULL);
+					if (!ExecCheck(ri_GeneratedExprs[i] , econtext))
+						ereport(ERROR,
+								errcode(ERRCODE_NOT_NULL_VIOLATION),
+								errmsg("column \"%s\" of table \"%s\" contains null values",
+										NameStr(attr->attname),
+										RelationGetRelationName(testrel)),
+								errtablecol(testrel, attnum));
+				}
 			}
+
+			ResetExprContext(econtext);
 		}
 		ExecDropSingleTupleTableSlot(slot);
 		table_endscan(scan);
@@ -3178,6 +3274,8 @@ validateDomainNotNullConstraint(Oid domainoid)
 		/* Close each rel after processing, but keep lock */
 		table_close(testrel, NoLock);
 	}
+
+	FreeExecutorState(estate);
 }
 
 /*
@@ -3214,6 +3312,33 @@ validateDomainCheckConstraint(Oid domainoid, const char *ccbin)
 		TupleTableSlot *slot;
 		TableScanDesc scan;
 		Snapshot	snapshot;
+		ExprState **ri_GeneratedExprs = NULL;
+
+		/* cacahe virtual generated columns handling */
+		if (HasDomainOverVirtualGenerated(rtc))
+		{
+			ri_GeneratedExprs = (ExprState **) palloc0(rtc->natts * sizeof(ExprState *));
+			for (int i = 0; i < rtc->natts; i++)
+			{
+				int			attnum = rtc->atts[i];
+
+				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+				if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+				{
+					Expr	   *defexpr;
+
+					defexpr = (Expr *) build_generation_expression(testrel, attnum);
+
+					/*
+					 * we need evaluate generation expression error soft way,
+					 * otherwise evaulation failure error would be "value for
+					 * domain violate check constraints", which is not helpful
+					 * error message while validating table domain data.
+					*/
+					ri_GeneratedExprs[i] = ExecPrepareExprSafe(defexpr, estate);
+				}
+			}
+		}
 
 		/* Scan all tuples in this relation */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
@@ -3232,31 +3357,63 @@ validateDomainCheckConstraint(Oid domainoid, const char *ccbin)
 				Datum		conResult;
 				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
 
-				d = slot_getattr(slot, attnum, &isNull);
-
-				econtext->domainValue_datum = d;
-				econtext->domainValue_isNull = isNull;
-
-				conResult = ExecEvalExprSwitchContext(exprstate,
-													  econtext,
-													  &isNull);
-
-				if (!isNull && !DatumGetBool(conResult))
+				/*
+				 * compute the virtual generated column expression.
+				 */
+				if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
 				{
 					/*
-					 * In principle the auxiliary information for this error
-					 * should be errdomainconstraint(), but errtablecol()
-					 * seems considerably more useful in practice.  Since this
-					 * code only executes in an ALTER DOMAIN command, the
-					 * client should already know which domain is in question,
-					 * and which constraint too.
-					 */
-					ereport(ERROR,
-							(errcode(ERRCODE_CHECK_VIOLATION),
-							 errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint",
-									NameStr(attr->attname),
-									RelationGetRelationName(testrel)),
-							 errtablecol(testrel, attnum)));
+					 * we'll use the EState's per-tuple context for evaluating
+					 * domain check constraint over virtual generated column
+					 * (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;
+
+					conResult = ExecEvalExprSwitchContext(ri_GeneratedExprs[i],
+														  econtext,
+														  &isNull);
+					if (SOFT_ERROR_OCCURRED(ri_GeneratedExprs[i]->escontext))
+					{
+						isNull = true;
+						ereport(ERROR,
+								errcode(ERRCODE_CHECK_VIOLATION),
+								errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint",
+										NameStr(attr->attname),
+										RelationGetRelationName(testrel)),
+								errtablecol(testrel, attnum));
+					}
+				}
+				else
+				{
+					d = slot_getattr(slot, attnum, &isNull);
+
+					econtext->domainValue_datum = d;
+					econtext->domainValue_isNull = isNull;
+
+					conResult = ExecEvalExprSwitchContext(exprstate,
+														  econtext,
+														  &isNull);
+
+					if (!isNull && !DatumGetBool(conResult))
+					{
+						/*
+						 * In principle the auxiliary information for this error
+						 * should be errdomainconstraint(), but errtablecol()
+						 * seems considerably more useful in practice.  Since this
+						 * code only executes in an ALTER DOMAIN command, the
+						 * client should already know which domain is in question,
+						 * and which constraint too.
+						*/
+						ereport(ERROR,
+								(errcode(ERRCODE_CHECK_VIOLATION),
+								errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint",
+										NameStr(attr->attname),
+										RelationGetRelationName(testrel)),
+								errtablecol(testrel, attnum)));
+					}
 				}
 			}
 
@@ -3273,6 +3430,49 @@ validateDomainCheckConstraint(Oid domainoid, const char *ccbin)
 	FreeExecutorState(estate);
 }
 
+/*
+ * domain over virtual generated column will wrap the expanded generation
+ * expression with a CoerceToDomain node. Evaulation of CoerceToDomain will also
+ * evaluate any NOT VALID domain constraints (see load_domaintype_info and
+ * lookup_type_cache). As a result, any future SELECT statement on that virtual
+ * column will effectively validate all NOT VALID constraints on that domain.
+ * Therefore, NOT VALID domain constraints are not supported on virtual
+ * generated columns.
+*/
+static void
+DomainNotValidConstrOnVirtual(Oid domainoid, const char *domainName)
+{
+	List	   *rels;
+	ListCell   *rt;
+
+	/* Fetch relation list with attributes based on this domain */
+	/* ShareUpdateExclusiveLock is sufficient in here. */
+	rels = get_rels_with_domain(domainoid, ShareUpdateExclusiveLock);
+
+	foreach(rt, rels)
+	{
+		RelToCheck *rtc = (RelToCheck *) lfirst(rt);
+		Relation	testrel = rtc->rel;
+		TupleDesc	tupdesc = RelationGetDescr(testrel);
+
+		if (tupdesc->constr && tupdesc->constr->has_generated_virtual)
+		{
+			for (int i = 0; i < rtc->natts; i++)
+			{
+				int			attnum = rtc->atts[i];
+				Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+				if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+					ereport(ERROR,
+							errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							errmsg("can not add NOT VALID constraint for domain \"%s\"", domainName),
+							errdetail("Domain with NOT VALID constraint over virtual generated column are not supported."));
+			}
+		}
+		table_close(testrel, ShareUpdateExclusiveLock);
+	}
+}
+
 /*
  * get_rels_with_domain
  *
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ab41ad8a8bc..83fec1a0cc5 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -67,6 +67,7 @@
 #include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 
@@ -415,7 +416,7 @@ ExecCheckTIDVisible(EState *estate,
  *
  * This fills the resultRelInfo's ri_GeneratedExprsI/ri_NumGeneratedNeededI or
  * ri_GeneratedExprsU/ri_NumGeneratedNeededU fields, depending on cmdtype.
- * This is used only for stored generated columns.
+ * This is used for stored and virtual generated columns.
  *
  * If cmdType == CMD_UPDATE, the ri_extraUpdatedCols field is filled too.
  * This is used by both stored and virtual generated columns.
@@ -497,6 +498,38 @@ ExecInitGenerated(ResultRelInfo *resultRelInfo,
 				ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
 				ri_NumGeneratedNeeded++;
 			}
+			else
+			{
+				/*
+				 * Virtual generated columns only need to be computed when their
+				 * type is domain_with_constraint. Simple domain with no
+				 * constraint, computation is not need.  However,
+				 * domain_with_constraint can be as the element type of a range,
+				 * composite, or array type.
+				 *
+				 * To determine whether the base element type is
+				 * domain_with_constraint, multiple lookup_type_cache calls
+				 * seems doable. However, this may add a lot of complexity So
+				 * simplified it: only if the type is TYPTYPE_BASE and it's not
+				 * an array type, computation is not needed.
+				*/
+				bool	has_domain_constraints = false;
+				Oid		typelem	= InvalidOid;
+				Oid		atttypid = TupleDescAttr(tupdesc, i)->atttypid;
+				char	att_typtype = get_typtype(atttypid);
+
+				if (att_typtype == TYPTYPE_BASE)
+					typelem = get_element_type(atttypid);
+				else if (att_typtype == TYPTYPE_DOMAIN)
+					has_domain_constraints = DomainHasConstraints(atttypid);
+
+				if (att_typtype != TYPTYPE_BASE || OidIsValid(typelem) ||
+					has_domain_constraints)
+				{
+					ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
+					ri_NumGeneratedNeeded++;
+				}
+			}
 
 			/* If UPDATE, mark column in resultRelInfo->ri_extraUpdatedCols */
 			if (cmdtype == CMD_UPDATE)
@@ -538,7 +571,9 @@ ExecInitGenerated(ResultRelInfo *resultRelInfo,
 
 /*
  * Compute generated columns for a tuple.
- * we might support virtual generated column in future, currently not.
+ * Generally, we don't need compute virtual generated column, except for column
+ * type is domain with constraint. Since virtual generated column don't have
+ * storage, we don't need stored the computed value.
  */
 void
 ExecComputeGenerated(ResultRelInfo *resultRelInfo, EState *estate,
@@ -554,7 +589,8 @@ ExecComputeGenerated(ResultRelInfo *resultRelInfo, EState *estate,
 	bool	   *nulls;
 
 	/* We should not be called unless this is true */
-	Assert(tupdesc->constr && tupdesc->constr->has_generated_stored);
+	Assert(tupdesc->constr);
+	Assert(tupdesc->constr->has_generated_stored || tupdesc->constr->has_generated_virtual);
 
 	/*
 	 * Initialize the expressions if we didn't already, and check whether we
@@ -572,8 +608,8 @@ ExecComputeGenerated(ResultRelInfo *resultRelInfo, EState *estate,
 	{
 		if (resultRelInfo->ri_GeneratedExprsI == NULL)
 			ExecInitGenerated(resultRelInfo, estate, cmdtype);
-		/* Early exit is impossible given the prior Assert */
-		Assert(resultRelInfo->ri_NumGeneratedNeededI > 0);
+		if (resultRelInfo->ri_NumGeneratedNeededI == 0)
+			return;
 		ri_GeneratedExprs = resultRelInfo->ri_GeneratedExprsI;
 	}
 
@@ -594,21 +630,28 @@ ExecComputeGenerated(ResultRelInfo *resultRelInfo, EState *estate,
 			Datum		val;
 			bool		isnull;
 
-			Assert(TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED);
-
 			econtext->ecxt_scantuple = slot;
 
 			val = ExecEvalExpr(ri_GeneratedExprs[i], econtext, &isnull);
 
-			/*
-			 * We must make a copy of val as we have no guarantees about where
-			 * memory for a pass-by-reference Datum is located.
-			 */
-			if (!isnull)
-				val = datumCopy(val, attr->attbyval, attr->attlen);
+			if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
+			{
+				/*
+				 * We must make a copy of val as we have no guarantees about where
+				 * memory for a pass-by-reference Datum is located.
+				*/
+				if (!isnull)
+					val = datumCopy(val, attr->attbyval, attr->attlen);
 
-			values[i] = val;
-			nulls[i] = isnull;
+				values[i] = val;
+				nulls[i] = isnull;
+			}
+			else
+			{
+				Assert(TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL);
+				values[i] = (Datum) 0;
+				nulls[i] = true;
+			}
 		}
 		else
 		{
@@ -930,7 +973,8 @@ ExecInsert(ModifyTableContext *context,
 		 * Compute stored generated columns
 		 */
 		if (resultRelationDesc->rd_att->constr &&
-			resultRelationDesc->rd_att->constr->has_generated_stored)
+			(resultRelationDesc->rd_att->constr->has_generated_stored ||
+			 resultRelationDesc->rd_att->constr->has_generated_virtual))
 			ExecComputeGenerated(resultRelInfo, estate, slot,
 								 CMD_INSERT);
 
@@ -1057,7 +1101,8 @@ ExecInsert(ModifyTableContext *context,
 		 * Compute stored generated columns
 		 */
 		if (resultRelationDesc->rd_att->constr &&
-			resultRelationDesc->rd_att->constr->has_generated_stored)
+			(resultRelationDesc->rd_att->constr->has_generated_stored ||
+			 resultRelationDesc->rd_att->constr->has_generated_virtual))
 			ExecComputeGenerated(resultRelInfo, estate, slot,
 								 CMD_INSERT);
 
@@ -2142,10 +2187,11 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
 	slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 
 	/*
-	 * Compute stored generated columns
+	 * Compute generated columns
 	 */
 	if (resultRelationDesc->rd_att->constr &&
-		resultRelationDesc->rd_att->constr->has_generated_stored)
+		(resultRelationDesc->rd_att->constr->has_generated_stored ||
+		 resultRelationDesc->rd_att->constr->has_generated_virtual))
 		ExecComputeGenerated(resultRelInfo, estate, slot,
 							 CMD_UPDATE);
 }
diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out
index ccbcdf8403f..73388ce28a9 100644
--- a/src/test/regress/expected/fast_default.out
+++ b/src/test/regress/expected/fast_default.out
@@ -70,6 +70,13 @@ NOTICE:  rewriting table has_volatile for reason 4
 -- stored generated columns need a rewrite
 ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored;
 NOTICE:  rewriting table has_volatile for reason 2
+-- virtual generated columns over volatile domain constraint no need rewrite.
+-- it will do table scan to validate domain constraints
+CREATE DOMAIN d1 as INT CHECK(value < random(min=>12::int, max=>21));
+CREATE DOMAIN d2 as INT;
+ALTER TABLE has_volatile ADD col8 d1 GENERATED ALWAYS AS (20 + id) VIRTUAL; --error
+ERROR:  value for domain d1 violates check constraint "d1_check"
+ALTER TABLE has_volatile ADD col8 d2 GENERATED ALWAYS AS (1 + id) VIRTUAL; --ok
 -- Test a large sample of different datatypes
 CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
 SELECT set('t');
@@ -922,6 +929,8 @@ DROP FUNCTION set(name);
 DROP FUNCTION comp();
 DROP TABLE m;
 DROP TABLE has_volatile;
+DROP DOMAIN d1;
+DROP DOMAIN d2;
 DROP EVENT TRIGGER has_volatile_rewrite;
 DROP FUNCTION log_rewrite;
 DROP SCHEMA fast_default;
diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out
index 6300e7c1d96..fb79c2fbac1 100644
--- a/src/test/regress/expected/generated_virtual.out
+++ b/src/test/regress/expected/generated_virtual.out
@@ -790,21 +790,69 @@ ERROR:  relation "gtest23p" does not exist
 --INSERT INTO gtest23q VALUES (1, 2);  -- ok
 --INSERT INTO gtest23q VALUES (2, 5);  -- error
 -- domains
-CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
-CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) VIRTUAL);
-ERROR:  virtual generated column "b" cannot have a domain type
---INSERT INTO gtest24 (a) VALUES (4);  -- ok
---INSERT INTO gtest24 (a) VALUES (6);  -- error
+CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10) DEFAULT 12;
+CREATE TABLE gtest24 (a int UNIQUE, b gtestdomain1 GENERATED ALWAYS AS (a * 2) VIRTUAL);
+INSERT INTO gtest24 (a, b) VALUES (4, default);  -- ok
+INSERT INTO gtest24 (a) VALUES (3), (NULL);  -- ok
+INSERT INTO gtest24 (a) VALUES (6);  -- error
+ERROR:  value for domain gtestdomain1 violates check constraint "gtestdomain1_check"
+UPDATE gtest24 SET a = 6;            -- error
+ERROR:  value for domain gtestdomain1 violates check constraint "gtestdomain1_check"
+COPY gtest24 FROM stdin;  --error
+ERROR:  value for domain gtestdomain1 violates check constraint "gtestdomain1_check"
+CONTEXT:  COPY gtest24, line 1: "6"
+SELECT * FROM gtest24 ORDER BY a;
+ a | b 
+---+---
+ 3 | 6
+ 4 | 8
+   |  
+(3 rows)
+
+--ALTER DOMAIN ADD CONSTRAINT variant.
+ALTER DOMAIN gtestdomain1 ADD CONSTRAINT cc CHECK (VALUE < 7) NOT VALID; --error
+ERROR:  can not add NOT VALID constraint for domain "gtestdomain1"
+DETAIL:  Domain with NOT VALID constraint over virtual generated column are not supported.
+ALTER DOMAIN gtestdomain1 ADD CHECK (value IS NULL);  --error
+ERROR:  column "b" of table "gtest24" contains values that violate the new constraint
+ALTER DOMAIN gtestdomain1 ADD CHECK (value IS NOT NULL); --error
+ERROR:  column "b" of table "gtest24" contains values that violate the new constraint
+ALTER DOMAIN gtestdomain1 ADD NOT NULL; --error
+ERROR:  column "b" of table "gtest24" contains null values
+DELETE FROM gtest24 WHERE a is NULL;
+ALTER DOMAIN gtestdomain1 ADD NOT NULL; --ok
+ALTER DOMAIN gtestdomain1 ADD CHECK (value IS NOT NULL);  --ok
+ALTER DOMAIN gtestdomain1 ADD CHECK (VALUE < 7); --error
+ERROR:  column "b" of table "gtest24" contains values that violate the new constraint
+ALTER DOMAIN gtestdomain1 ADD CHECK (VALUE < 9); --ok
 CREATE TYPE gtestdomain1range AS range (subtype = gtestdomain1);
-CREATE TABLE gtest24r (a int PRIMARY KEY, b gtestdomain1range GENERATED ALWAYS AS (gtestdomain1range(a, a + 5)) VIRTUAL);
-ERROR:  virtual generated column "b" cannot have a domain type
---INSERT INTO gtest24r (a) VALUES (4);  -- ok
---INSERT INTO gtest24r (a) VALUES (6);  -- error
+CREATE TABLE gtest24r (a int PRIMARY KEY, b gtestdomain1range GENERATED ALWAYS AS (gtestdomain1range(a, a + 4)) VIRTUAL);
+INSERT INTO gtest24r (a) VALUES (4);  -- ok
+INSERT INTO gtest24r (a) VALUES (6);  -- error
+ERROR:  value for domain gtestdomain1 violates check constraint "gtestdomain1_check"
+INSERT INTO gtest24r (a) VALUES (5);  -- error
+ERROR:  value for domain gtestdomain1 violates check constraint "gtestdomain1_check2"
+CREATE TABLE gtest24v (
+  a jsonb,
+  b gtestdomain1[] GENERATED ALWAYS AS (JSON_QUERY(a, '$.a' returning gtestdomain1[] error on error)) VIRTUAL,
+  c gtestdomain1[] GENERATED ALWAYS AS (JSON_QUERY(a, '$.b' returning gtestdomain1[])) VIRTUAL NOT NULL);
+INSERT INTO gtest24v VALUES (jsonb '{"a":[6,10], "b":[6,2]}'); --error
+ERROR:  value for domain gtestdomain1 violates check constraint "gtestdomain1_check"
+INSERT INTO gtest24v VALUES (jsonb '{"a":[6,-1], "b":[6,10]}'); --error
+ERROR:  null value in column "c" of relation "gtest24v" violates not-null constraint
+DETAIL:  Failing row contains ({"a": [6, -1], "b": [6, 10]}, virtual, virtual).
+INSERT INTO gtest24v VALUES (jsonb '{"a":[6,-1], "b":[6,2]}'); --ok
 CREATE DOMAIN gtestdomainnn AS int CHECK (VALUE IS NOT NULL);
 CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) VIRTUAL);
-ERROR:  virtual generated column "b" cannot have a domain type
---INSERT INTO gtest24nn (a) VALUES (4);  -- ok
---INSERT INTO gtest24nn (a) VALUES (NULL);  -- error
+INSERT INTO gtest24nn (a) VALUES (4);  -- ok
+INSERT INTO gtest24nn (a) VALUES (NULL);  -- error
+ERROR:  value for domain gtestdomainnn violates check constraint "gtestdomainnn_check"
+--ALTER TABLE ADD COLUM variant.
+ALTER TABLE gtest24nn ADD COLUMN c gtestdomainnn GENERATED ALWAYS AS (nullif(a, 4)) virtual; --error
+ERROR:  value for domain gtestdomainnn violates check constraint "gtestdomainnn_check"
+ALTER TABLE gtest24nn ADD COLUMN c gtestdomainnn GENERATED ALWAYS AS (a * 3) virtual check (c < 10); --error
+ERROR:  check constraint "gtest24nn_c_check" of relation "gtest24nn" is violated by some row
+ALTER TABLE gtest24nn ADD COLUMN c gtestdomainnn GENERATED ALWAYS AS (a * 2) virtual NOT NULL; --ok
 -- typed tables (currently not supported)
 CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
 CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) VIRTUAL);
diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql
index 068dd0bc8aa..a34c96271ef 100644
--- a/src/test/regress/sql/fast_default.sql
+++ b/src/test/regress/sql/fast_default.sql
@@ -78,6 +78,13 @@ ALTER TABLE has_volatile ALTER COLUMN col1 SET DATA TYPE float8,
 ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored;
 
 
+-- virtual generated columns over volatile domain constraint no need rewrite.
+-- it will do table scan to validate domain constraints
+CREATE DOMAIN d1 as INT CHECK(value < random(min=>12::int, max=>21));
+CREATE DOMAIN d2 as INT;
+ALTER TABLE has_volatile ADD col8 d1 GENERATED ALWAYS AS (20 + id) VIRTUAL; --error
+ALTER TABLE has_volatile ADD col8 d2 GENERATED ALWAYS AS (1 + id) VIRTUAL; --ok
+
 
 -- Test a large sample of different datatypes
 CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
@@ -616,6 +623,8 @@ DROP FUNCTION set(name);
 DROP FUNCTION comp();
 DROP TABLE m;
 DROP TABLE has_volatile;
+DROP DOMAIN d1;
+DROP DOMAIN d2;
 DROP EVENT TRIGGER has_volatile_rewrite;
 DROP FUNCTION log_rewrite;
 DROP SCHEMA fast_default;
diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql
index b4eedeee2fb..3ebef11ac4f 100644
--- a/src/test/regress/sql/generated_virtual.sql
+++ b/src/test/regress/sql/generated_virtual.sql
@@ -444,19 +444,51 @@ CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
 --INSERT INTO gtest23q VALUES (2, 5);  -- error
 
 -- domains
-CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
-CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) VIRTUAL);
---INSERT INTO gtest24 (a) VALUES (4);  -- ok
---INSERT INTO gtest24 (a) VALUES (6);  -- error
+CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10) DEFAULT 12;
+CREATE TABLE gtest24 (a int UNIQUE, b gtestdomain1 GENERATED ALWAYS AS (a * 2) VIRTUAL);
+INSERT INTO gtest24 (a, b) VALUES (4, default);  -- ok
+INSERT INTO gtest24 (a) VALUES (3), (NULL);  -- ok
+INSERT INTO gtest24 (a) VALUES (6);  -- error
+UPDATE gtest24 SET a = 6;            -- error
+COPY gtest24 FROM stdin;  --error
+6
+\.
+
+SELECT * FROM gtest24 ORDER BY a;
+
+--ALTER DOMAIN ADD CONSTRAINT variant.
+ALTER DOMAIN gtestdomain1 ADD CONSTRAINT cc CHECK (VALUE < 7) NOT VALID; --error
+ALTER DOMAIN gtestdomain1 ADD CHECK (value IS NULL);  --error
+ALTER DOMAIN gtestdomain1 ADD CHECK (value IS NOT NULL); --error
+ALTER DOMAIN gtestdomain1 ADD NOT NULL; --error
+DELETE FROM gtest24 WHERE a is NULL;
+ALTER DOMAIN gtestdomain1 ADD NOT NULL; --ok
+ALTER DOMAIN gtestdomain1 ADD CHECK (value IS NOT NULL);  --ok
+ALTER DOMAIN gtestdomain1 ADD CHECK (VALUE < 7); --error
+ALTER DOMAIN gtestdomain1 ADD CHECK (VALUE < 9); --ok
+
 CREATE TYPE gtestdomain1range AS range (subtype = gtestdomain1);
-CREATE TABLE gtest24r (a int PRIMARY KEY, b gtestdomain1range GENERATED ALWAYS AS (gtestdomain1range(a, a + 5)) VIRTUAL);
---INSERT INTO gtest24r (a) VALUES (4);  -- ok
---INSERT INTO gtest24r (a) VALUES (6);  -- error
+CREATE TABLE gtest24r (a int PRIMARY KEY, b gtestdomain1range GENERATED ALWAYS AS (gtestdomain1range(a, a + 4)) VIRTUAL);
+INSERT INTO gtest24r (a) VALUES (4);  -- ok
+INSERT INTO gtest24r (a) VALUES (6);  -- error
+INSERT INTO gtest24r (a) VALUES (5);  -- error
+
+CREATE TABLE gtest24v (
+  a jsonb,
+  b gtestdomain1[] GENERATED ALWAYS AS (JSON_QUERY(a, '$.a' returning gtestdomain1[] error on error)) VIRTUAL,
+  c gtestdomain1[] GENERATED ALWAYS AS (JSON_QUERY(a, '$.b' returning gtestdomain1[])) VIRTUAL NOT NULL);
+INSERT INTO gtest24v VALUES (jsonb '{"a":[6,10], "b":[6,2]}'); --error
+INSERT INTO gtest24v VALUES (jsonb '{"a":[6,-1], "b":[6,10]}'); --error
+INSERT INTO gtest24v VALUES (jsonb '{"a":[6,-1], "b":[6,2]}'); --ok
 
 CREATE DOMAIN gtestdomainnn AS int CHECK (VALUE IS NOT NULL);
 CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) VIRTUAL);
---INSERT INTO gtest24nn (a) VALUES (4);  -- ok
---INSERT INTO gtest24nn (a) VALUES (NULL);  -- error
+INSERT INTO gtest24nn (a) VALUES (4);  -- ok
+INSERT INTO gtest24nn (a) VALUES (NULL);  -- error
+--ALTER TABLE ADD COLUM variant.
+ALTER TABLE gtest24nn ADD COLUMN c gtestdomainnn GENERATED ALWAYS AS (nullif(a, 4)) virtual; --error
+ALTER TABLE gtest24nn ADD COLUMN c gtestdomainnn GENERATED ALWAYS AS (a * 3) virtual check (c < 10); --error
+ALTER TABLE gtest24nn ADD COLUMN c gtestdomainnn GENERATED ALWAYS AS (a * 2) virtual NOT NULL; --ok
 
 -- typed tables (currently not supported)
 CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint);
-- 
2.34.1

From 63f2c3411f1e53a6f27b92a0e17d4b77095e089d Mon Sep 17 00:00:00 2001
From: jian he <jian.universal...@gmail.com>
Date: Wed, 21 May 2025 10:55:02 +0800
Subject: [PATCH v2 2/3] soft error variant of ExecPrepareExpr, ExecInitExpr

Introduce function ExecPrepareExprSafe and ExecInitExprSafe.
ExecPrepareExprSafe initialize for expression execution with soft error support.
not all expression node support it. some like CoerceToDomain do support it.

discussion: https://postgr.es/m/CACJufxFNUHxuSE=g20c2alo3d+4t_j9h0x8esmirzcc4frl...@mail.gmail.com
---
 src/backend/executor/execExpr.c | 63 +++++++++++++++++++++++++++++++++
 src/include/executor/executor.h |  2 ++
 2 files changed, 65 insertions(+)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index f1569879b52..b8f5f647281 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -170,6 +170,46 @@ ExecInitExpr(Expr *node, PlanState *parent)
 	return state;
 }
 
+/*
+ * ExecInitExpr: soft error variant of ExecInitExpr.
+ * use it only for expression nodes support soft errors, not all expression
+ * nodes support it.
+*/
+ExprState *
+ExecInitExprSafe(Expr *node, PlanState *parent)
+{
+	ExprState  *state;
+	ExprEvalStep scratch = {0};
+
+	/* Special case: NULL expression produces a NULL ExprState pointer */
+	if (node == NULL)
+		return NULL;
+
+	/* Initialize ExprState with empty step list */
+	state = makeNode(ExprState);
+	state->expr = node;
+	state->parent = parent;
+	state->ext_params = NULL;
+	state->escontext = makeNode(ErrorSaveContext);
+	state->escontext->type = T_ErrorSaveContext;
+	state->escontext->error_occurred = false;
+	state->escontext->details_wanted = true;
+
+	/* Insert setup steps as needed */
+	ExecCreateExprSetupSteps(state, (Node *) node);
+
+	/* Compile the expression proper */
+	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+	/* Finally, append a DONE step */
+	scratch.opcode = EEOP_DONE_RETURN;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return state;
+}
+
 /*
  * ExecInitExprWithParams: prepare a standalone expression tree for execution
  *
@@ -778,6 +818,29 @@ ExecPrepareExpr(Expr *node, EState *estate)
 	return result;
 }
 
+/*
+ * ExecPrepareExprSafe: soft error variant of ExecPrepareExpr.
+ *
+ * use it when expression node *support* soft error expression execution.
+ * ExecPrepareExpr comments apply to here too.
+ */
+ExprState *
+ExecPrepareExprSafe(Expr *node, EState *estate)
+{
+	ExprState  *result;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	node = expression_planner(node);
+
+	result = ExecInitExprSafe(node, NULL);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return result;
+}
+
 /*
  * ExecPrepareQual --- initialize for qual execution outside a normal
  * Plan tree context.
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ae99407db89..a26160042ee 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -346,6 +346,7 @@ ExecProcNode(PlanState *node)
  * prototypes from functions in execExpr.c
  */
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprSafe(Expr *node, PlanState *parent);
 extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
 extern ExprState *ExecInitQual(List *qual, PlanState *parent);
 extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
@@ -394,6 +395,7 @@ extern ProjectionInfo *ExecBuildUpdateProjection(List *targetList,
 												 TupleTableSlot *slot,
 												 PlanState *parent);
 extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
+extern ExprState *ExecPrepareExprSafe(Expr *node, EState *estate);
 extern ExprState *ExecPrepareQual(List *qual, EState *estate);
 extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
 extern List *ExecPrepareExprList(List *nodes, EState *estate);
-- 
2.34.1

Reply via email to