From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Fri, 8 May 2026 00:00:00 +0000
Subject: [PATCH v1] Disallow whole-row index references with virtual generated columns

DefineIndex rejects indexes on virtual generated columns when they are
referenced directly in index expressions or predicates, but it missed
whole-row Vars.  A whole-row Var on a relation with any virtual generated
column includes that column, so allowing it can bypass the virtual-column
index restriction.

In particular, a partial unique index such as WHERE rel IS NOT NULL can be
created on a table with a virtual generated column.  The predicate can be true
at the SQL level, but index build and maintenance evaluate the stored
predicate against the physical heap tuple, where the virtual column is not
stored.  Such an index can therefore contain no entries for rows that
satisfy the predicate, leaving uniqueness unenforced.

Reject whole-row Vars in index expressions and predicates when the indexed
relation has virtual generated columns.  Keep whole-row references allowed
for relations that have no virtual generated columns.

Add regression tests for a whole-row expression index and a partial unique
index predicate using a whole-row reference.

---
 src/backend/commands/indexcmds.c                | 9 +++++++--
 src/test/regress/expected/generated_virtual.out | 4 ++++
 src/test/regress/sql/generated_virtual.sql      | 2 ++
 3 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..572dc89c98e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1140,11 +1140,15 @@ DefineIndex(ParseState *pstate,
 	 */
 	if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
 	{
+		TupleDesc	tupdesc = RelationGetDescr(rel);
 		Bitmapset  *indexattrs = NULL;
+		bool		has_virtual_generated_columns;
 		int			j;
 
 		pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
 		pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
+		has_virtual_generated_columns = tupdesc->constr &&
+			tupdesc->constr->has_generated_virtual;
 
 		for (int i = FirstLowInvalidHeapAttributeNumber + 1; i < 0; i++)
 		{
@@ -1165,8 +1169,9 @@ DefineIndex(ParseState *pstate,
 		{
 			AttrNumber	attno = j + FirstLowInvalidHeapAttributeNumber;
 
-			if (attno > 0 &&
-				TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+			if ((attno == 0 && has_virtual_generated_columns) ||
+				(attno > 0 &&
+				 TupleDescAttr(tupdesc, attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL))
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 stmt->isconstraint ?
diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out
index 24d5dbf46ca..763a11c88db 100644
--- a/src/test/regress/expected/generated_virtual.out
+++ b/src/test/regress/expected/generated_virtual.out
@@ -776,6 +776,10 @@ CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
 --CREATE INDEX gtest22c_b_idx ON gtest22c (b);
 --CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
 --CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
+CREATE INDEX gtest22c_row_idx ON gtest22c ((gtest22c));
+ERROR:  indexes on virtual generated columns are not supported
+CREATE UNIQUE INDEX gtest22c_row_pred_idx ON gtest22c (a) WHERE gtest22c IS NOT NULL;
+ERROR:  indexes on virtual generated columns are not supported
 --\d gtest22c
 --INSERT INTO gtest22c VALUES (1), (2), (3);
 --SET enable_seqscan TO off;
diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql
index 9c2bb6590b3..24643ad153f 100644
--- a/src/test/regress/sql/generated_virtual.sql
+++ b/src/test/regress/sql/generated_virtual.sql
@@ -408,6 +408,8 @@ CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
 --CREATE INDEX gtest22c_b_idx ON gtest22c (b);
 --CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
 --CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
+CREATE INDEX gtest22c_row_idx ON gtest22c ((gtest22c));
+CREATE UNIQUE INDEX gtest22c_row_pred_idx ON gtest22c (a) WHERE gtest22c IS NOT NULL;
 --\d gtest22c
 
 --INSERT INTO gtest22c VALUES (1), (2), (3);
