v2-0001-v2-enhance-index-support-for-virtual-generated-co.patch

application/octet-stream

Filename: v2-0001-v2-enhance-index-support-for-virtual-generated-co.patch
Type: application/octet-stream
Part: 7
Message: Re: support create index on virtual generated column.

Patch

Format: format-patch
Series: patch v2-0001
Subject: v2 enhance-index-support-for-virtual-generated-columns
File+
src/backend/access/heap/heapam_handler.c 12 8
src/backend/bootstrap/bootstrap.c 6 0
src/backend/catalog/index.c 50 11
src/backend/catalog/indexing.c 2 0
src/backend/catalog/toasting.c 4 0
src/backend/commands/analyze.c 5 5
src/backend/commands/indexcmds.c 17 25
src/backend/executor/execIndexing.c 11 2
src/backend/nodes/makefuncs.c 9 1
src/backend/optimizer/path/indxpath.c 9 9
src/backend/optimizer/plan/createplan.c 3 3
src/backend/optimizer/util/plancat.c 18 4
src/backend/utils/adt/selfuncs.c 2 2
src/backend/utils/cache/relcache.c 126 0
src/include/nodes/execnodes.h 4 0
src/include/nodes/pathnodes.h 2 0
src/include/utils/relcache.h 4 0
src/include/utils/rel.h 2 0
From 1098404a9b7f963b8e1c685f8166b76dd4c6cc82 Mon Sep 17 00:00:00 2001
From: Zizhuan Liu <44973863@qq.com>
Date: Sun, 5 Jul 2026 10:30:49 +0800
Subject: [PATCH v2] v2 enhance-index-support-for-virtual-generated-columns

---
 src/backend/access/heap/heapam_handler.c |  20 ++--
 src/backend/bootstrap/bootstrap.c        |   6 ++
 src/backend/catalog/index.c              |  61 +++++++++--
 src/backend/catalog/indexing.c           |   2 +
 src/backend/catalog/toasting.c           |   4 +
 src/backend/commands/analyze.c           |  10 +-
 src/backend/commands/indexcmds.c         |  42 +++-----
 src/backend/executor/execIndexing.c      |  13 ++-
 src/backend/nodes/makefuncs.c            |  10 +-
 src/backend/optimizer/path/indxpath.c    |  18 ++--
 src/backend/optimizer/plan/createplan.c  |   6 +-
 src/backend/optimizer/util/plancat.c     |  22 +++-
 src/backend/utils/adt/selfuncs.c         |   4 +-
 src/backend/utils/cache/relcache.c       | 126 +++++++++++++++++++++++
 src/include/nodes/execnodes.h            |   4 +
 src/include/nodes/pathnodes.h            |   2 +
 src/include/utils/rel.h                  |   2 +
 src/include/utils/relcache.h             |   4 +
 18 files changed, 286 insertions(+), 70 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2268cc27..22e0762e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1159,7 +1159,7 @@ heapam_index_build_range_scan(Relation heapRelation,
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
 	double		reltuples;
-	ExprState  *predicate;
+	ExprState  *predicateExpand;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -1200,7 +1200,7 @@ heapam_index_build_range_scan(Relation heapRelation,
 	econtext->ecxt_scantuple = slot;
 
 	/* Set up execution state for predicate, if any. */
-	predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+	predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
 
 	/*
 	 * Prepare for scan of the base relation.  In a normal index build, we use
@@ -1607,9 +1607,9 @@ heapam_index_build_range_scan(Relation heapRelation,
 		 * In a partial index, discard tuples that don't satisfy the
 		 * predicate.
 		 */
-		if (predicate != NULL)
+		if (predicateExpand != NULL)
 		{
-			if (!ExecQual(predicate, econtext))
+			if (!ExecQual(predicateExpand, econtext))
 				continue;
 		}
 
@@ -1709,7 +1709,9 @@ heapam_index_build_range_scan(Relation heapRelation,
 
 	/* These may have been pointing to the now-gone estate */
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsExpandState = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateExpandState = NULL;
 
 	return reltuples;
 }
@@ -1726,7 +1728,7 @@ heapam_index_validate_scan(Relation heapRelation,
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	ExprState  *predicate;
+	ExprState  *predicateExpand;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -1758,7 +1760,7 @@ heapam_index_validate_scan(Relation heapRelation,
 	econtext->ecxt_scantuple = slot;
 
 	/* Set up execution state for predicate, if any. */
-	predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+	predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
 
 	/*
 	 * Prepare for scan of the base relation.  We need just those tuples
@@ -1895,9 +1897,9 @@ heapam_index_validate_scan(Relation heapRelation,
 			 * In a partial index, discard tuples that don't satisfy the
 			 * predicate.
 			 */
-			if (predicate != NULL)
+			if (predicateExpand != NULL)
 			{
-				if (!ExecQual(predicate, econtext))
+				if (!ExecQual(predicateExpand, econtext))
 					continue;
 			}
 
@@ -1952,7 +1954,9 @@ heapam_index_validate_scan(Relation heapRelation,
 
 	/* These may have been pointing to the now-gone estate */
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsExpandState = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateExpandState = NULL;
 }
 
 /*
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b0dcd987..80bf4fc4 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1156,11 +1156,17 @@ index_register(Oid heap,
 	/* expressions will likely be null, but may as well copy it */
 	newind->il_info->ii_Expressions =
 		copyObject(indexInfo->ii_Expressions);
+	newind->il_info->ii_ExpressionsExpand =
+		copyObject(indexInfo->ii_ExpressionsExpand);
 	newind->il_info->ii_ExpressionsState = NIL;
+	newind->il_info->ii_ExpressionsExpandState = NIL;
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate =
 		copyObject(indexInfo->ii_Predicate);
+	newind->il_info->ii_PredicateExpand =
+		copyObject(indexInfo->ii_PredicateExpand);
 	newind->il_info->ii_PredicateState = NULL;
+	newind->il_info->ii_PredicateExpandState = NULL;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357..ee260fde 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1403,6 +1403,10 @@ index_create_copy(Relation heapRelation, uint16 flags,
 							concurrently,	/* concurrent */
 							indexRelation->rd_indam->amsummarizing,
 							oldInfo->ii_WithoutOverlaps);
+	newInfo->ii_ExpressionsExpand =
+		ExpandVirtualGeneratedColumns(newInfo->ii_ExpressionsExpand, heapRelation, InvalidOid);
+	newInfo->ii_PredicateExpand =
+		ExpandVirtualGeneratedColumns(newInfo->ii_PredicateExpand, heapRelation, InvalidOid);
 
 	/* fetch exclusion constraint info if any */
 	if (indexRelation->rd_index->indisexclusion)
@@ -2471,6 +2475,8 @@ BuildIndexInfo(Relation index)
 					   false,
 					   index->rd_indam->amsummarizing,
 					   indexStruct->indisexclusion && indexStruct->indisunique);
+	ii->ii_ExpressionsExpand = (List *)RelationGetIndexExpressionsExpand(index);
+	ii->ii_PredicateExpand = (List *)RelationGetIndexPredicateExpand(index);
 
 	/* fill in attribute numbers */
 	for (i = 0; i < numAtts; i++)
@@ -2760,10 +2766,12 @@ FormIndexDatum(IndexInfo *indexInfo,
 		/* First time through, set up expression evaluation state */
 		indexInfo->ii_ExpressionsState =
 			ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+		indexInfo->ii_ExpressionsExpandState =
+			ExecPrepareExprList(indexInfo->ii_ExpressionsExpand, estate);
 		/* Check caller has set up context correctly */
 		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
 	}
-	indexpr_item = list_head(indexInfo->ii_ExpressionsState);
+	indexpr_item = list_head(indexInfo->ii_ExpressionsExpandState);
 
 	for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 	{
@@ -2775,11 +2783,40 @@ FormIndexDatum(IndexInfo *indexInfo,
 			iDatum = slot_getsysattr(slot, keycol, &isNull);
 		else if (keycol != 0)
 		{
-			/*
-			 * Plain index column; get the value we need directly from the
-			 * heap tuple.
-			 */
-			iDatum = slot_getattr(slot, keycol, &isNull);
+			TupleDesc			tupdesc = slot->tts_tupleDescriptor;
+			Form_pg_attribute 	att = TupleDescAttr(tupdesc, keycol - 1);
+
+			if (att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
+			{
+				/*
+				 * Plain index column; get the value we need directly from the
+				 * heap tuple.
+				*/
+				iDatum = slot_getattr(slot, keycol, &isNull);
+			}
+			else
+			{
+				TupleConstr 	*constr = tupdesc->constr;
+
+				for (int j = 0; j < constr->num_defval; j++)
+				{
+					AttrDefault 	*defval;
+					Expr	   		*expr = NULL;
+					ExprState  		*exprstate;
+
+					defval = &constr->defval[j];
+					if (defval->adnum == keycol)
+					{
+						expr = stringToNode(defval->adbin);
+						exprstate = ExecPrepareExpr(expr, estate);
+						iDatum = ExecEvalExprSwitchContext(exprstate,
+														   GetPerTupleExprContext(estate),
+														   &isNull);
+						break;
+					}
+					Assert(j + 1 < constr->num_defval);
+				}
+			}
 		}
 		else
 		{
@@ -2791,7 +2828,7 @@ FormIndexDatum(IndexInfo *indexInfo,
 			iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexpr_item),
 											   GetPerTupleExprContext(estate),
 											   &isNull);
-			indexpr_item = lnext(indexInfo->ii_ExpressionsState, indexpr_item);
+			indexpr_item = lnext(indexInfo->ii_ExpressionsExpandState, indexpr_item);
 		}
 		values[i] = iDatum;
 		isnull[i] = isNull;
@@ -3220,7 +3257,7 @@ IndexCheckExclusion(Relation heapRelation,
 	TableScanDesc scan;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	ExprState  *predicate;
+	ExprState  *predicateExpand;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -3246,7 +3283,7 @@ IndexCheckExclusion(Relation heapRelation,
 	econtext->ecxt_scantuple = slot;
 
 	/* Set up execution state for predicate, if any. */
-	predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+	predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
 
 	/*
 	 * Scan all live tuples in the base relation.
@@ -3266,9 +3303,9 @@ IndexCheckExclusion(Relation heapRelation,
 		/*
 		 * In a partial index, ignore tuples that don't satisfy the predicate.
 		 */
-		if (predicate != NULL)
+		if (predicateExpand != NULL)
 		{
-			if (!ExecQual(predicate, econtext))
+			if (!ExecQual(predicateExpand, econtext))
 				continue;
 		}
 
@@ -3301,7 +3338,9 @@ IndexCheckExclusion(Relation heapRelation,
 
 	/* These may have been pointing to the now-gone estate */
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsExpandState = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateExpandState = NULL;
 }
 
 /*
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index fd7d2ec0..d27aaa09 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -133,7 +133,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 		 * supported, nor exclusion constraints, nor deferred uniqueness
 		 */
 		Assert(indexInfo->ii_Expressions == NIL);
+		Assert(indexInfo->ii_ExpressionsExpand == NIL);
 		Assert(indexInfo->ii_Predicate == NIL);
+		Assert(indexInfo->ii_PredicateExpand == NIL);
 		Assert(indexInfo->ii_ExclusionOps == NULL);
 		Assert(index->rd_index->indimmediate);
 		Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 4aa52a4b..daebfda1 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -298,9 +298,13 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_IndexAttrNumbers[0] = 1;
 	indexInfo->ii_IndexAttrNumbers[1] = 2;
 	indexInfo->ii_Expressions = NIL;
+	indexInfo->ii_ExpressionsExpand = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
+	indexInfo->ii_ExpressionsExpandState = NIL;
 	indexInfo->ii_Predicate = NIL;
+	indexInfo->ii_PredicateExpand = NIL;
 	indexInfo->ii_PredicateState = NULL;
+	indexInfo->ii_PredicateExpandState = NULL;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b7..fc67c24a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -899,7 +899,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		TupleTableSlot *slot;
 		EState	   *estate;
 		ExprContext *econtext;
-		ExprState  *predicate;
+		ExprState  *predicateExpand;
 		Datum	   *exprvals;
 		bool	   *exprnulls;
 		int			numindexrows,
@@ -908,7 +908,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		double		totalindexrows;
 
 		/* Ignore index if no columns to analyze and not partial */
-		if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL)
+		if (attr_cnt == 0 && indexInfo->ii_PredicateExpand == NIL)
 			continue;
 
 		/*
@@ -926,7 +926,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		econtext->ecxt_scantuple = slot;
 
 		/* Set up execution state for predicate. */
-		predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+		predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
 
 		/* Compute and save index expression values */
 		exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
@@ -949,9 +949,9 @@ compute_index_stats(Relation onerel, double totalrows,
 			ExecStoreHeapTuple(heapTuple, slot, false);
 
 			/* If index is partial, check predicate */
-			if (predicate != NULL)
+			if (predicateExpand != NULL)
 			{
-				if (!ExecQual(predicate, econtext))
+				if (!ExecQual(predicateExpand, econtext))
 					continue;
 			}
 			numindexrows++;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8d..8a63efc8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -932,6 +932,8 @@ DefineIndex(ParseState *pstate,
 							  concurrent,
 							  amissummarizing,
 							  stmt->iswithoutoverlaps);
+	indexInfo->ii_PredicateExpand =
+		ExpandVirtualGeneratedColumns(indexInfo->ii_PredicateExpand, rel, InvalidOid);
 
 	typeIds = palloc_array(Oid, numberOfAttributes);
 	collationIds = palloc_array(Oid, numberOfAttributes);
@@ -948,6 +950,8 @@ DefineIndex(ParseState *pstate,
 					  root_save_userid, root_save_sec_context,
 					  &root_save_nestlevel);
 
+	indexInfo->ii_ExpressionsExpand =
+		ExpandVirtualGeneratedColumns(indexInfo->ii_ExpressionsExpand, rel, InvalidOid);
 	/*
 	 * Extra checks when creating a PRIMARY KEY index.
 	 */
@@ -1110,7 +1114,7 @@ DefineIndex(ParseState *pstate,
 	 * We disallow indexes on system columns.  They would not necessarily get
 	 * updated correctly, and they don't seem useful anyway.
 	 *
-	 * Also disallow virtual generated columns in indexes (use expression
+	 * Aisallow virtual generated columns in indexes (include using expression
 	 * index instead).
 	 */
 	for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1123,7 +1127,7 @@ DefineIndex(ParseState *pstate,
 					 errmsg("index creation on system columns is not supported")));
 
 
-		if (attno > 0 &&
+		/*if (attno > 0 && i < numberOfKeyAttributes &&
 			TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
 			ereport(ERROR,
 					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1131,17 +1135,15 @@ DefineIndex(ParseState *pstate,
 					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"));
+					errmsg("indexes on virtual generated columns are not supported"));*/
 	}
 
 	/*
-	 * Also check for system and generated columns used in expressions or
-	 * predicates.
+	 * Also check for system columns used in expressions or predicates.
 	 */
 	if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
 	{
 		Bitmapset  *indexattrs = NULL;
-		int			j;
 
 		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
 		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
@@ -1154,25 +1156,6 @@ DefineIndex(ParseState *pstate,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("index creation on system columns is not supported")));
 		}
-
-		/*
-		 * XXX Virtual generated columns in index expressions or predicates
-		 * could be supported, but it needs support in
-		 * RelationGetIndexExpressions() and RelationGetIndexPredicate().
-		 */
-		j = -1;
-		while ((j = bms_next_member(indexattrs, j)) >= 0)
-		{
-			AttrNumber	attno = j + FirstLowInvalidHeapAttributeNumber;
-
-			if (attno > 0 &&
-				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")));
-		}
 	}
 
 	/* Is index safe for others to ignore?  See set_indexsafe_procflags() */
@@ -1876,6 +1859,11 @@ CheckPredicate(Expr *predicate)
  * If the caller switched to the table owner, ddl_userid is the role for ACL
  * checks reached without traversing opaque expressions.  Otherwise, it's
  * InvalidOid, and other ddl_* arguments are undefined.
+ *
+ * Upon returning from this function, callers must apply
+ * ExpandVirtualGeneratedColumns() to ii_ExpressionsExpand
+ * when necessary for actual expansion if ii_ExpressionsExpand
+ * is not NIL or dummy.
  */
 static void
 ComputeIndexAttrs(ParseState *pstate,
@@ -2274,6 +2262,10 @@ ComputeIndexAttrs(ParseState *pstate,
 
 		attn++;
 	}
+
+	indexInfo->ii_ExpressionsExpand =
+		ExpandVirtualGeneratedColumns(copyObject(indexInfo->ii_Expressions),
+											  NULL, relId);
 }
 
 /*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index eb383812..b47daaf5 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -380,20 +380,24 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
 		if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
+			ExprState  *predicateExpand;
 
 			/*
 			 * If predicate state not set up yet, create it (in the estate's
 			 * per-query context)
 			 */
 			predicate = indexInfo->ii_PredicateState;
+			predicateExpand = indexInfo->ii_PredicateExpandState;
 			if (predicate == NULL)
 			{
 				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+				predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
 				indexInfo->ii_PredicateState = predicate;
+				indexInfo->ii_PredicateExpandState = predicateExpand;
 			}
 
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!ExecQual(predicateExpand, econtext))
 				continue;
 		}
 
@@ -616,20 +620,24 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 		if (indexInfo->ii_Predicate != NIL)
 		{
 			ExprState  *predicate;
+			ExprState  *predicateExpand;
 
 			/*
 			 * If predicate state not set up yet, create it (in the estate's
 			 * per-query context)
 			 */
 			predicate = indexInfo->ii_PredicateState;
+			predicateExpand = indexInfo->ii_PredicateExpandState;
 			if (predicate == NULL)
 			{
 				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+				predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
 				indexInfo->ii_PredicateState = predicate;
+				indexInfo->ii_PredicateExpandState = predicateExpand;
 			}
 
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext))
+			if (!ExecQual(predicateExpand, econtext))
 				continue;
 		}
 
@@ -1102,6 +1110,7 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
 	 * pass hint.
 	 */
 	idxExprs = RelationGetIndexExpressions(indexRelation);
+	idxExprs = list_concat(idxExprs, RelationGetIndexExpressionsExpand(indexRelation));
 	hasexpression = index_expression_changed_walker((Node *) idxExprs,
 													allUpdatedCols);
 	list_free(idxExprs);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40b09958..946891b9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -828,7 +828,11 @@ make_ands_implicit(Expr *clause)
 
 /*
  * makeIndexInfo
- *	  create an IndexInfo node
+ *	  create an IndexInfo node.  Upon returning from this function,
+ * callers must apply ExpandVirtualGeneratedColumns() 
+ * or RelationGetIndexExpressionsExpand or RelationGetIndexPredicateExpand() to
+ * ii_ExpressionsExpand and ii_PredicateExpand as needed for actual
+ * expansion when they are not NIL or dummy.
  */
 IndexInfo *
 makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
@@ -856,11 +860,15 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
 
 	/* expressions */
 	n->ii_Expressions = expressions;
+	n->ii_ExpressionsExpand = copyObject(expressions);
 	n->ii_ExpressionsState = NIL;
+	n->ii_ExpressionsExpandState = NIL;
 
 	/* predicates  */
 	n->ii_Predicate = predicates;
+	n->ii_PredicateExpand = copyObject(predicates);
 	n->ii_PredicateState = NULL;
+	n->ii_PredicateExpandState = NULL;
 
 	/* exclusion constraints */
 	n->ii_ExclusionOps = NULL;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3..b716ee92 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1119,7 +1119,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
 		 * just scanning the predOK index alone, no OR.)
 		 */
 		useful_predicate = false;
-		if (index->indpred != NIL)
+		if (index->indpredExpand != NIL)
 		{
 			if (index->predOK)
 			{
@@ -1131,10 +1131,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
 				if (all_clauses == NIL)
 					all_clauses = list_concat_copy(clauses, other_clauses);
 
-				if (!predicate_implied_by(index->indpred, all_clauses, false))
+				if (!predicate_implied_by(index->indpredExpand, all_clauses, false))
 					continue;	/* can't use it at all */
 
-				if (!predicate_implied_by(index->indpred, other_clauses, false))
+				if (!predicate_implied_by(index->indpredExpand, other_clauses, false))
 					useful_predicate = true;
 			}
 		}
@@ -2183,7 +2183,7 @@ find_indexpath_quals(Path *bitmapqual, List **quals, List **preds)
 
 			*quals = lappend(*quals, iclause->rinfo->clause);
 		}
-		*preds = list_concat(*preds, ipath->indexinfo->indpred);
+		*preds = list_concat(*preds, ipath->indexinfo->indpredExpand);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
@@ -4038,11 +4038,11 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
 		IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
 		ListCell   *lcr;
 
-		if (index->indpred == NIL)
+		if (index->indpredExpand == NIL)
 			continue;			/* ignore non-partial indexes here */
 
 		if (!index->predOK)		/* don't repeat work if already proven OK */
-			index->predOK = predicate_implied_by(index->indpred, clauselist,
+			index->predOK = predicate_implied_by(index->indpredExpand, clauselist,
 												 false);
 
 		/* If rel is an update target, leave indrestrictinfo as set above */
@@ -4068,7 +4068,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
 			/* predicate_implied_by() assumes first arg is immutable */
 			if (contain_mutable_functions((Node *) rinfo->clause) ||
 				!predicate_implied_by(list_make1(rinfo->clause),
-									  index->indpred, false))
+									  index->indpredExpand, false))
 				index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);
 		}
 	}
@@ -4402,14 +4402,14 @@ match_index_to_operand(Node *operand,
 		int			i;
 		Node	   *indexkey;
 
-		indexpr_item = list_head(index->indexprs);
+		indexpr_item = list_head(index->indexprsExpand);
 		for (i = 0; i < indexcol; i++)
 		{
 			if (index->indexkeys[i] == 0)
 			{
 				if (indexpr_item == NULL)
 					elog(ERROR, "wrong number of index expressions");
-				indexpr_item = lnext(index->indexprs, indexpr_item);
+				indexpr_item = lnext(index->indexprsExpand, indexpr_item);
 			}
 		}
 		if (indexpr_item == NULL)
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183d..2bdad7bd 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3340,7 +3340,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 				subindexECs = lappend(subindexECs, rinfo->parent_ec);
 		}
 		/* We can add any index predicate conditions, too */
-		foreach(l, ipath->indexinfo->indpred)
+		foreach(l, ipath->indexinfo->indpredExpand)
 		{
 			Expr	   *pred = (Expr *) lfirst(l);
 
@@ -5142,7 +5142,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
 	}
 
 	/* It's an index expression, so find and cross-check the expression */
-	indexpr_item = list_head(index->indexprs);
+	indexpr_item = list_head(index->indexprsExpand);
 	for (pos = 0; pos < index->ncolumns; pos++)
 	{
 		if (index->indexkeys[pos] == 0)
@@ -5167,7 +5167,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
 				else
 					elog(ERROR, "index key does not match expected index column");
 			}
-			indexpr_item = lnext(index->indexprs, indexpr_item);
+			indexpr_item = lnext(index->indexprsExpand, indexpr_item);
 		}
 	}
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7c4be174..72846d9e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -433,24 +433,38 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			 * properly reduced.
 			 */
 			info->indexprs = RelationGetIndexExpressions(indexRelation);
+			info->indexprsExpand = RelationGetIndexExpressionsExpand(indexRelation);
 			info->indpred = RelationGetIndexPredicate(indexRelation);
+			info->indpredExpand = RelationGetIndexPredicateExpand(indexRelation);
 			if (info->indexprs)
 			{
 				if (varno != 1)
+				{
 					ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
+					ChangeVarNodes((Node *) info->indexprsExpand, 1, varno, 0);
+				}
 
 				info->indexprs = (List *)
 					eval_const_expressions(root, (Node *) info->indexprs);
+				info->indexprsExpand = (List *)
+					eval_const_expressions(root, (Node *) info->indexprsExpand);
 			}
 			if (info->indpred)
 			{
 				if (varno != 1)
+				{
 					ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
+					ChangeVarNodes((Node *) info->indpredExpand, 1, varno, 0);
+				}
 
 				info->indpred = (List *)
 					eval_const_expressions(root,
 										   (Node *) make_ands_explicit(info->indpred));
+				info->indpredExpand = (List *)
+					eval_const_expressions(root,
+										   (Node *) make_ands_explicit(info->indpredExpand));
 				info->indpred = make_ands_implicit((Expr *) info->indpred);
+				info->indpredExpand = make_ands_implicit((Expr *) info->indpredExpand);
 			}
 
 			/* Build targetlist using the completed indexprs data */
@@ -941,8 +955,8 @@ infer_arbiter_indexes(PlannerInfo *root)
 										   attno - FirstLowInvalidHeapAttributeNumber);
 				}
 
-				inferElems = RelationGetIndexExpressions(idxRel);
-				inferIndexExprs = RelationGetIndexPredicate(idxRel);
+				inferElems = RelationGetIndexExpressionsExpand(idxRel);
+				inferIndexExprs = RelationGetIndexPredicateExpand(idxRel);
 				break;
 			}
 		}
@@ -1072,7 +1086,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 			continue;
 
 		/* Expression attributes (if any) must match */
-		idxExprs = RelationGetIndexExpressions(idxRel);
+		idxExprs = RelationGetIndexExpressionsExpand(idxRel);
 		if (idxExprs)
 		{
 			if (varno != 1)
@@ -1139,7 +1153,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 		if (list_difference(idxExprs, inferElems) != NIL)
 			continue;
 
-		predExprs = RelationGetIndexPredicate(idxRel);
+		predExprs = RelationGetIndexPredicateExpand(idxRel);
 		if (predExprs)
 		{
 			if (varno != 1)
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d6efd070..98e7216e 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -7639,10 +7639,10 @@ add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals)
 	List	   *predExtraQuals = NIL;
 	ListCell   *lc;
 
-	if (index->indpred == NIL)
+	if (index->indpredExpand == NIL)
 		return indexQuals;
 
-	foreach(lc, index->indpred)
+	foreach(lc, index->indpredExpand)
 	{
 		Node	   *predQual = (Node *) lfirst(lc);
 		List	   *oneQual = list_make1(predQual);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0572ab42..e604bb91 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -91,6 +91,7 @@
 #include "utils/resowner.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
 
 #define RELCACHE_INIT_FILEMAGIC		0x573266	/* version ID value */
 
@@ -1583,7 +1584,9 @@ RelationInitIndexAccessInfo(Relation relation)
 	 * expressions, predicate, exclusion caches will be filled later
 	 */
 	relation->rd_indexprs = NIL;
+	relation->rd_indexprsExpand = NIL;
 	relation->rd_indpred = NIL;
+	relation->rd_indpredExpand = NIL;
 	relation->rd_exclops = NULL;
 	relation->rd_exclprocs = NULL;
 	relation->rd_exclstrats = NULL;
@@ -5146,6 +5149,45 @@ RelationGetIndexExpressions(Relation relation)
 	return result;
 }
 
+List *
+RelationGetIndexExpressionsExpand(Relation relation)
+{
+	List	   *result;
+	MemoryContext oldcxt;
+	bool		isnull;
+	Datum		heapRelidDatum;
+	Oid         heapRelid;
+
+	/* Quick exit if we already computed the result. */
+	if (relation->rd_indexprsExpand)
+		return copyObject(relation->rd_indexprsExpand);
+
+	/* Quick exit if there is nothing to do. */
+	if (relation->rd_indextuple == NULL ||
+		heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
+		return NIL;
+
+	result = RelationGetIndexExpressions(relation);
+	if (result == NIL)
+		return NIL;
+
+	heapRelidDatum = heap_getattr(relation->rd_indextuple,
+							 Anum_pg_index_indrelid,
+							 GetPgIndexDescriptor(),
+							 &isnull);
+	Assert(!isnull);
+	heapRelid = DatumGetObjectId(heapRelidDatum);
+
+	result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+	/* Now save a copy of the completed tree in the relcache entry. */
+	oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+	relation->rd_indexprsExpand = copyObject(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
 /*
  * RelationGetDummyIndexExpressions -- get dummy expressions for an index
  *
@@ -5266,6 +5308,45 @@ RelationGetIndexPredicate(Relation relation)
 	return result;
 }
 
+List *
+RelationGetIndexPredicateExpand(Relation relation)
+{
+	List	   *result;
+	MemoryContext oldcxt;
+	bool		isnull;
+	Datum		heapRelidDatum;
+	Oid         heapRelid;
+
+	/* Quick exit if we already computed the result. */
+	if (relation->rd_indpredExpand)
+		return copyObject(relation->rd_indpredExpand);
+
+	/* Quick exit if there is nothing to do. */
+	if (relation->rd_indextuple == NULL ||
+		heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL))
+		return NIL;
+
+	result = RelationGetIndexPredicate(relation);
+	if (result == NIL)
+		return NIL;
+
+	heapRelidDatum = heap_getattr(relation->rd_indextuple,
+							 Anum_pg_index_indrelid,
+							 GetPgIndexDescriptor(),
+							 &isnull);
+	Assert(!isnull);
+	heapRelid = DatumGetObjectId(heapRelidDatum);
+
+	result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+	/* Now save a copy of the completed tree in the relcache entry. */
+	oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+	relation->rd_indpredExpand = copyObject(result);
+	MemoryContextSwitchTo(oldcxt);
+
+	return result;
+}
+
 /*
  * RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
  *
@@ -6490,7 +6571,9 @@ load_relcache_init_file(bool shared)
 		rel->rd_partcheckvalid = false;
 		rel->rd_partcheckcxt = NULL;
 		rel->rd_indexprs = NIL;
+		rel->rd_indexprsExpand = NIL;
 		rel->rd_indpred = NIL;
+		rel->rd_indpredExpand = NIL;
 		rel->rd_exclops = NULL;
 		rel->rd_exclprocs = NULL;
 		rel->rd_exclstrats = NULL;
@@ -7019,3 +7102,46 @@ ResOwnerReleaseRelation(Datum res)
 
 	RelationCloseCleanup((Relation) DatumGetPointer(res));
 }
+
+List *
+ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId)
+{
+	bool 				 opened_relation = false;
+	TupleDesc			 tupdesc;
+
+	if (list == NIL || (heapRelation == NULL && heapRelId == InvalidOid))
+		return list;
+
+	if (heapRelation == NULL)
+	{
+		heapRelation = table_open(heapRelId, NoLock);
+		opened_relation = true;
+	}
+
+	tupdesc = RelationGetDescr(heapRelation);
+	if ((tupdesc->constr && tupdesc->constr->has_generated_virtual))
+	{
+		int				 j;
+		Bitmapset		*indexattrs = NULL;
+
+		pull_varattnos((Node *)list, 1, &indexattrs);
+
+		j = -1;
+		while ((j = bms_next_member(indexattrs, j)) >= 0)
+		{
+			AttrNumber	attno = j + FirstLowInvalidHeapAttributeNumber;
+
+			if (attno > 0 &&
+				TupleDescAttr(tupdesc, attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+			{
+				list = (List *)expand_generated_columns_in_expr((Node *)list, heapRelation, 1);
+				break;
+			}
+		}
+	}
+
+	if (opened_relation)
+		table_close(heapRelation, NoLock);
+
+	return list;
+}
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 53c13831..20a2e618 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -190,13 +190,17 @@ typedef struct IndexInfo
 
 	/* expr trees for expression entries, or NIL if none */
 	List	   *ii_Expressions; /* list of Expr */
+	List	   *ii_ExpressionsExpand; 	/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
+	List	   *ii_ExpressionsExpandState;	/* list of ExprState */
 
 	/* partial-index predicate, or NIL if none */
 	List	   *ii_Predicate;	/* list of Expr */
+	List	   *ii_PredicateExpand;		/* list of Expr */
 	/* exec state for expressions, or NIL if none */
 	ExprState  *ii_PredicateState;
+	ExprState  *ii_PredicateExpandState;
 
 	/* Per-column exclusion operators, or NULL if none */
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c681..9c65e123 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,8 +1403,10 @@ typedef struct IndexOptInfo
 	 * print indextlist
 	 */
 	List	   *indexprs pg_node_attr(read_write_ignore);
+	List	   *indexprsExpand pg_node_attr(read_write_ignore);
 	/* predicate if a partial index, else NIL */
 	List	   *indpred;
+	List	   *indpredExpand;
 
 	/* targetlist representing index columns */
 	List	   *indextlist;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa07ebf8..7bd01b36 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -210,7 +210,9 @@ typedef struct RelationData
 	struct FmgrInfo *rd_supportinfo;	/* lookup info for support procedures */
 	int16	   *rd_indoption;	/* per-column AM-specific flags */
 	List	   *rd_indexprs;	/* index expression trees, if any */
+	List	   *rd_indexprsExpand;	/* expanded index expression trees, if any */
 	List	   *rd_indpred;		/* index predicate tree, if any */
+	List	   *rd_indpredExpand;		/* expanded index predicate tree, if any */
 	Oid		   *rd_exclops;		/* OIDs of exclusion operators, if any */
 	Oid		   *rd_exclprocs;	/* OIDs of exclusion ops' procs, if any */
 	uint16	   *rd_exclstrats;	/* exclusion ops' strategy numbers, if any */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 89c27aa1..70531bb8 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -58,8 +58,10 @@ extern List *RelationGetStatExtList(Relation relation);
 extern Oid	RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok);
 extern Oid	RelationGetReplicaIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
+extern List *RelationGetIndexExpressionsExpand(Relation relation);
 extern List *RelationGetDummyIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
+extern List *RelationGetIndexPredicateExpand(Relation relation);
 extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
 
 /*
@@ -161,4 +163,6 @@ extern PGDLLIMPORT bool criticalRelcachesBuilt;
 /* should be used only by relcache.c and postinit.c */
 extern PGDLLIMPORT bool criticalSharedRelcachesBuilt;
 
+extern List *ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId);
+
 #endif							/* RELCACHE_H */
-- 
2.43.0