On Tue, Dec 16, 2025 at 9:01 PM Amul Sul <[email protected]> wrote:
> >
>
> +1, patch looks quite straightforward and pretty much reasonable to me.
>
> Regards,
> Amul

hi.

some failures because I didn't adjust collate.linux.utf8.out and
collate.windows.win1252.out:
https://cirrus-ci.com/build/6690791764000768
https://api.cirrus-ci.com/v1/artifact/task/5658162642026496/testrun/build/testrun/regress/regress/regression.diffs
https://api.cirrus-ci.com/v1/artifact/task/5605386083893248/log/src/test/regress/regression.diffs

The attached patch only adjusts collate.linux.utf8.out and
collate.windows.win1252.out, with no other changes.


--
jian
https://www.enterprisedb.com
From 2cbd83fd0cc6683a6cf0066cb8556639db939bfb Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 31 Dec 2025 13:36:44 +0800
Subject: [PATCH v2 2/2] Error position support for index specifications

Add support for error position reporting for index specifications.

comitfest: https://commitfest.postgresql.org/patch/6322
discussion: https://postgr.es/m/cacjufxh3ogxf1hrzgaawyntye2jhemk9jbtrtgv-kjk6tsg...@mail.gmail.com
---
 src/backend/bootstrap/bootparse.y             |  6 +-
 src/backend/commands/indexcmds.c              | 60 ++++++++++++-------
 src/backend/commands/tablecmds.c              |  9 ++-
 src/backend/tcop/utility.c                    |  3 +-
 src/include/commands/defrem.h                 |  3 +-
 src/test/regress/expected/alter_table.out     |  4 ++
 .../regress/expected/collate.icu.utf8.out     |  2 +
 .../regress/expected/collate.linux.utf8.out   |  2 +
 src/test/regress/expected/collate.out         |  2 +
 .../expected/collate.windows.win1252.out      |  2 +
 src/test/regress/expected/insert_conflict.out |  6 +-
 .../regress/expected/sqljson_queryfuncs.out   | 56 +++++++++++++++++
 12 files changed, 125 insertions(+), 30 deletions(-)

diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index da0e7dea497..063faecadc1 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -308,7 +308,8 @@ Boot_DeclareIndexStmt:
 					relationId = RangeVarGetRelid(stmt->relation, NoLock,
 												  false);
 
-					DefineIndex(relationId,
+					DefineIndex(NULL,
+								relationId,
 								stmt,
 								$4,
 								InvalidOid,
@@ -361,7 +362,8 @@ Boot_DeclareUniqueIndexStmt:
 					relationId = RangeVarGetRelid(stmt->relation, NoLock,
 												  false);
 
-					DefineIndex(relationId,
+					DefineIndex(NULL,
+								relationId,
 								stmt,
 								$5,
 								InvalidOid,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 67a451463ed..ef564e9e3bf 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -75,7 +75,8 @@
 /* non-export function prototypes */
 static bool CompareOpclassOptions(const Datum *opts1, const Datum *opts2, int natts);
 static void CheckPredicate(Expr *predicate);
-static void ComputeIndexAttrs(IndexInfo *indexInfo,
+static void ComputeIndexAttrs(ParseState *pstate,
+							  IndexInfo *indexInfo,
 							  Oid *typeOids,
 							  Oid *collationOids,
 							  Oid *opclassOids,
@@ -248,7 +249,7 @@ CheckIndexCompatible(Oid oldId,
 	opclassIds = palloc_array(Oid, numberOfAttributes);
 	opclassOptions = palloc_array(Datum, numberOfAttributes);
 	coloptions = palloc_array(int16, numberOfAttributes);
-	ComputeIndexAttrs(indexInfo,
+	ComputeIndexAttrs(NULL, indexInfo,
 					  typeIds, collationIds, opclassIds, opclassOptions,
 					  coloptions, attributeList,
 					  exclusionOpNames, relationId,
@@ -515,6 +516,7 @@ WaitForOlderSnapshots(TransactionId limitXmin, bool progress)
  * consider offering one DDL command for catalog setup and a separate DDL
  * command for steps that run opaque expressions.
  *
+ * 'pstate': pointer to ParseState struct for determining error position
  * 'tableId': the OID of the table relation on which the index is to be
  *		created
  * 'stmt': IndexStmt describing the properties of the new index.
@@ -538,7 +540,8 @@ WaitForOlderSnapshots(TransactionId limitXmin, bool progress)
  * Returns the object address of the created index.
  */
 ObjectAddress
-DefineIndex(Oid tableId,
+DefineIndex(ParseState *pstate,
+			Oid tableId,
 			IndexStmt *stmt,
 			Oid indexRelationId,
 			Oid parentIndexId,
@@ -933,7 +936,8 @@ DefineIndex(Oid tableId,
 	opclassIds = palloc_array(Oid, numberOfAttributes);
 	opclassOptions = palloc_array(Datum, numberOfAttributes);
 	coloptions = palloc_array(int16, numberOfAttributes);
-	ComputeIndexAttrs(indexInfo,
+	ComputeIndexAttrs(pstate,
+					  indexInfo,
 					  typeIds, collationIds, opclassIds, opclassOptions,
 					  coloptions, allIndexParams,
 					  stmt->excludeOpNames, tableId,
@@ -1517,7 +1521,8 @@ DefineIndex(Oid tableId,
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
-						DefineIndex(childRelid, childStmt,
+						DefineIndex(NULL,
+									childRelid, childStmt,
 									InvalidOid, /* no predefined OID */
 									indexRelationId,	/* this is our child */
 									createdConstraintId,
@@ -1866,7 +1871,8 @@ CheckPredicate(Expr *predicate)
  * InvalidOid, and other ddl_* arguments are undefined.
  */
 static void
-ComputeIndexAttrs(IndexInfo *indexInfo,
+ComputeIndexAttrs(ParseState *pstate,
+				  IndexInfo *indexInfo,
 				  Oid *typeOids,
 				  Oid *collationOids,
 				  Oid *opclassOids,
@@ -1951,12 +1957,14 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 					ereport(ERROR,
 							(errcode(ERRCODE_UNDEFINED_COLUMN),
 							 errmsg("column \"%s\" named in key does not exist",
-									attribute->name)));
+									attribute->name)),
+							parser_errposition(pstate, exprLocation((Node *) attribute)));
 				else
 					ereport(ERROR,
 							(errcode(ERRCODE_UNDEFINED_COLUMN),
 							 errmsg("column \"%s\" does not exist",
-									attribute->name)));
+									attribute->name)),
+							parser_errposition(pstate, exprLocation((Node *) attribute)));
 			}
 			attform = (Form_pg_attribute) GETSTRUCT(atttuple);
 			indexInfo->ii_IndexAttrNumbers[attn] = attform->attnum;
@@ -1974,7 +1982,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 			if (attn >= nkeycols)
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("expressions are not supported in included columns")));
+						 errmsg("expressions are not supported in included columns"),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 			atttype = exprType(expr);
 			attcollation = exprCollation(expr);
 
@@ -2015,7 +2024,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 				if (contain_mutable_functions_after_planning((Expr *) expr))
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-							 errmsg("functions in index expression must be marked IMMUTABLE")));
+							 errmsg("functions in index expression must be marked IMMUTABLE"),
+							 parser_errposition(pstate, exprLocation((Node *) attribute))));
 			}
 		}
 
@@ -2030,19 +2040,23 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 			if (attribute->collation)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("including column does not support a collation")));
+						 errmsg("including column does not support a collation"),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 			if (attribute->opclass)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("including column does not support an operator class")));
+						 errmsg("including column does not support an operator class"),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 			if (attribute->ordering != SORTBY_DEFAULT)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("including column does not support ASC/DESC options")));
+						 errmsg("including column does not support ASC/DESC options"),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 			if (attribute->nulls_ordering != SORTBY_NULLS_DEFAULT)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("including column does not support NULLS FIRST/LAST options")));
+						 errmsg("including column does not support NULLS FIRST/LAST options"),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 
 			opclassOids[attn] = InvalidOid;
 			opclassOptions[attn] = (Datum) 0;
@@ -2086,7 +2100,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_INDETERMINATE_COLLATION),
 						 errmsg("could not determine which collation to use for index expression"),
-						 errhint("Use the COLLATE clause to set the collation explicitly.")));
+						 errhint("Use the COLLATE clause to set the collation explicitly."),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 		}
 		else
 		{
@@ -2094,7 +2109,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("collations are not supported by type %s",
-								format_type_be(atttype))));
+								format_type_be(atttype)),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 		}
 
 		collationOids[attn] = attcollation;
@@ -2162,7 +2178,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("operator %s is not commutative",
 								format_operator(opid)),
-						 errdetail("Only commutative operators can be used in exclusion constraints.")));
+						 errdetail("Only commutative operators can be used in exclusion constraints."),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 
 			/*
 			 * Operator must be a member of the right opfamily, too
@@ -2175,7 +2192,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 						 errmsg("operator %s is not a member of operator family \"%s\"",
 								format_operator(opid),
 								get_opfamily_name(opfamily, false)),
-						 errdetail("The exclusion operator must be related to the index operator class for the constraint.")));
+						 errdetail("The exclusion operator must be related to the index operator class for the constraint."),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 
 			indexInfo->ii_ExclusionOps[attn] = opid;
 			indexInfo->ii_ExclusionProcs[attn] = get_opcode(opid);
@@ -2225,12 +2243,14 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("access method \"%s\" does not support ASC/DESC options",
-								accessMethodName)));
+								accessMethodName),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 			if (attribute->nulls_ordering != SORTBY_NULLS_DEFAULT)
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("access method \"%s\" does not support NULLS FIRST/LAST options",
-								accessMethodName)));
+								accessMethodName),
+						 parser_errposition(pstate, exprLocation((Node *) attribute))));
 		}
 
 		/* Set up the per-column opclass options (attoptions field). */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1d9565b09fc..32be3d3ca75 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1302,7 +1302,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			idxstmt =
 				generateClonedIndexStmt(NULL, idxRel,
 										attmap, &constraintOid);
-			DefineIndex(RelationGetRelid(rel),
+			DefineIndex(NULL,
+						RelationGetRelid(rel),
 						idxstmt,
 						InvalidOid,
 						RelationGetRelid(idxRel),
@@ -9666,7 +9667,8 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	/* suppress notices when rebuilding existing index */
 	quiet = is_rebuild;
 
-	address = DefineIndex(RelationGetRelid(rel),
+	address = DefineIndex(NULL,
+						  RelationGetRelid(rel),
 						  stmt,
 						  InvalidOid,	/* no predefined OID */
 						  InvalidOid,	/* no parent index */
@@ -20770,7 +20772,8 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
 			stmt = generateClonedIndexStmt(NULL,
 										   idxRel, attmap,
 										   &conOid);
-			DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
+			DefineIndex(NULL,
+						RelationGetRelid(attachrel), stmt, InvalidOid,
 						RelationGetRelid(idxRel),
 						conOid,
 						-1,
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index d18a3a60a46..b611eab0550 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1541,7 +1541,8 @@ ProcessUtilitySlow(ParseState *pstate,
 					/* ... and do it */
 					EventTriggerAlterTableStart(parsetree);
 					address =
-						DefineIndex(relid,	/* OID of heap relation */
+						DefineIndex(pstate,
+									relid,	/* OID of heap relation */
 									stmt,
 									InvalidOid, /* no predefined OID */
 									InvalidOid, /* no parent index */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index f3432b4b6a1..39afce90707 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -25,7 +25,8 @@
 extern void RemoveObjects(DropStmt *stmt);
 
 /* commands/indexcmds.c */
-extern ObjectAddress DefineIndex(Oid tableId,
+extern ObjectAddress DefineIndex(ParseState *pstate,
+								 Oid tableId,
 								 IndexStmt *stmt,
 								 Oid indexRelationId,
 								 Oid parentIndexId,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5e98bbf2425..ac1a7345d0f 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1585,8 +1585,12 @@ ERROR:  column "........pg.dropped.1........" referenced in foreign key constrai
 drop table atacc2;
 create index "testing_idx" on atacc1(a);
 ERROR:  column "a" does not exist
+LINE 1: create index "testing_idx" on atacc1(a);
+                                             ^
 create index "testing_idx" on atacc1("........pg.dropped.1........");
 ERROR:  column "........pg.dropped.1........" does not exist
+LINE 1: create index "testing_idx" on atacc1("........pg.dropped.1.....
+                                             ^
 -- test create as and select into
 insert into atacc1 values (21, 22, 23);
 create table attest1 as select * from atacc1;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 8023014fe63..1325e123877 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -996,6 +996,8 @@ CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "C")); -- this is d
 CREATE INDEX collate_test1_idx4 ON collate_test1 (((b||'foo') COLLATE "POSIX"));
 CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE "C"); -- fail
 ERROR:  collations are not supported by type integer
+LINE 1: CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE ...
+                                                          ^
 CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C")); -- fail
 ERROR:  collations are not supported by type integer
 LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C...
diff --git a/src/test/regress/expected/collate.linux.utf8.out b/src/test/regress/expected/collate.linux.utf8.out
index fbaab7cdf83..c6e84c27b69 100644
--- a/src/test/regress/expected/collate.linux.utf8.out
+++ b/src/test/regress/expected/collate.linux.utf8.out
@@ -1009,6 +1009,8 @@ CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "C")); -- this is d
 CREATE INDEX collate_test1_idx4 ON collate_test1 (((b||'foo') COLLATE "POSIX"));
 CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE "C"); -- fail
 ERROR:  collations are not supported by type integer
+LINE 1: CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE ...
+                                                          ^
 CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C")); -- fail
 ERROR:  collations are not supported by type integer
 LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C...
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index bf72908fbd3..25818f09ad2 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -596,6 +596,8 @@ CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "POSIX")); -- this
 CREATE INDEX collate_test1_idx4 ON collate_test1 (((b||'foo') COLLATE "POSIX"));
 CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE "POSIX"); -- fail
 ERROR:  collations are not supported by type integer
+LINE 1: CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE ...
+                                                          ^
 CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "POSIX")); -- fail
 ERROR:  collations are not supported by type integer
 LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "P...
diff --git a/src/test/regress/expected/collate.windows.win1252.out b/src/test/regress/expected/collate.windows.win1252.out
index 4644f56b31d..2a9e52a6b4a 100644
--- a/src/test/regress/expected/collate.windows.win1252.out
+++ b/src/test/regress/expected/collate.windows.win1252.out
@@ -845,6 +845,8 @@ CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "C")); -- this is d
 CREATE INDEX collate_test1_idx4 ON collate_test1 (((b||'foo') COLLATE "POSIX"));
 CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE "C"); -- fail
 ERROR:  collations are not supported by type integer
+LINE 1: CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE ...
+                                                          ^
 CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C")); -- fail
 ERROR:  collations are not supported by type integer
 LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "C...
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 91fbe91844d..b0e12962088 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -5,15 +5,15 @@ create table insertconflicttest(key int4, fruit text);
 -- invalid clauses
 insert into insertconflicttest values (1) on conflict (key int4_ops (fillfactor=10)) do nothing;
 ERROR:  operator class options are not allowed in ON CONFLICT clause
-LINE 1: ...rt into insertconflicttest values (1) on conflict (key int4_...
+LINE 1: ...t into insertconflicttest values (1) on conflict (key int4_o...
                                                              ^
 insert into insertconflicttest values (1) on conflict (key asc) do nothing;
 ERROR:  ASC/DESC is not allowed in ON CONFLICT clause
-LINE 1: ...rt into insertconflicttest values (1) on conflict (key asc) ...
+LINE 1: ...t into insertconflicttest values (1) on conflict (key asc) d...
                                                              ^
 insert into insertconflicttest values (1) on conflict (key nulls last) do nothing;
 ERROR:  NULLS FIRST/LAST is not allowed in ON CONFLICT clause
-LINE 1: ...rt into insertconflicttest values (1) on conflict (key nulls...
+LINE 1: ...t into insertconflicttest values (1) on conflict (key nulls ...
                                                              ^
 -- These things should work through a view, as well
 create view insertconflictview as select * from insertconflicttest;
diff --git a/src/test/regress/expected/sqljson_queryfuncs.out b/src/test/regress/expected/sqljson_queryfuncs.out
index 53145f50f18..d1b4b8d99f4 100644
--- a/src/test/regress/expected/sqljson_queryfuncs.out
+++ b/src/test/regress/expected/sqljson_queryfuncs.out
@@ -1153,69 +1153,125 @@ CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$'));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a[0]'));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.time()'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.tim...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.date()'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.time_tz()'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.tim...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.timestamp()'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.tim...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.timestamp_tz()'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.tim...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.date() < $.time_tz())'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.date() < $.time())'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.time() < $.time())'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.time() < $.time_tz())'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.timestamp() < $.timestamp_tz())'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.timestamp_tz() < $.timestamp_tz())'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.time() < $.datetime("HH:MI TZH"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.date() < $.datetime("HH:MI TZH"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.timestamp() < $.datetime("HH:MI TZH"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.timestamp() < $.datetime("HH:MI"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.timestamp_tz() < $.datetime("HH:MI TZH"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.timestamp_tz() < $.datetime("HH:MI"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.date() < $x' PASSING '12:34'::timetz AS x));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.date() < $x' PASSING '1234'::int AS x));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.timestamp(2) < $.timestamp(3))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()'));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@ < $.datetime())'));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime())'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime("HH:MI TZH"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("HH:MI TZH"))'));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI") < $.datetime("YY-MM-DD HH:MI"))'));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))'));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ?...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.dat...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ? (@ == $x)' PASSING '2020-07-14'::date AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, ...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, ...
+                                               ^
 CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x));
 CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' DEFAULT random()::int ON ERROR));
 ERROR:  functions in index expression must be marked IMMUTABLE
+LINE 1: CREATE INDEX ON test_jsonb_mutability (JSON_VALUE(js, '$' DE...
+                                               ^
 -- DEFAULT expression
 CREATE OR REPLACE FUNCTION ret_setint() RETURNS SETOF integer AS
 $$
-- 
2.34.1

From 6bd9a9a3b4dbb32ca01cc456ad5e5131c485b367 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Wed, 31 Dec 2025 16:05:20 +0800
Subject: [PATCH v2 1/2] add Location to IndexElem
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add location information to each IndexElem so that error reporting can precisely
identify the specific invalid element. This is particularly useful for
subsequent refactoring in ComputeIndexAttrs.

Author: Álvaro Herrera <alvherre(at)kurilemu(dot)de>

comitfest: https://commitfest.postgresql.org/patch/6322
discussion: https://postgr.es/m/cacjufxh3ogxf1hrzgaawyntye2jhemk9jbtrtgv-kjk6tsg...@mail.gmail.com
---
 src/backend/bootstrap/bootparse.y  | 1 +
 src/backend/nodes/nodeFuncs.c      | 3 +++
 src/backend/parser/gram.y          | 5 +++++
 src/backend/parser/parse_clause.c  | 8 ++++----
 src/backend/parser/parse_utilcmd.c | 4 ++++
 src/include/nodes/parsenodes.h     | 1 +
 6 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 9833f52c1be..da0e7dea497 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -415,6 +415,7 @@ boot_index_param:
 					n->opclass = list_make1(makeString($2));
 					n->ordering = SORTBY_DEFAULT;
 					n->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					n->location = -1;
 					$$ = n;
 				}
 		;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 024a2b2fd84..89b7d80c0aa 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1726,6 +1726,9 @@ exprLocation(const Node *expr)
 		case T_ColumnDef:
 			loc = ((const ColumnDef *) expr)->location;
 			break;
+		case T_IndexElem:
+			loc = ((const IndexElem *) expr)->location;
+			break;
 		case T_Constraint:
 			loc = ((const Constraint *) expr)->location;
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 28f4e11e30f..3f407e7d00f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8454,6 +8454,7 @@ index_elem_options:
 			$$->opclassopts = NIL;
 			$$->ordering = $3;
 			$$->nulls_ordering = $4;
+			$$->location = @1;
 		}
 	| opt_collate any_name reloptions opt_asc_desc opt_nulls_order
 		{
@@ -8466,6 +8467,7 @@ index_elem_options:
 			$$->opclassopts = $3;
 			$$->ordering = $4;
 			$$->nulls_ordering = $5;
+			$$->location = @1;
 		}
 	;
 
@@ -8478,16 +8480,19 @@ index_elem: ColId index_elem_options
 				{
 					$$ = $2;
 					$$->name = $1;
+					$$->location = @1;
 				}
 			| func_expr_windowless index_elem_options
 				{
 					$$ = $2;
 					$$->expr = $1;
+					$$->location = @1;
 				}
 			| '(' a_expr ')' index_elem_options
 				{
 					$$ = $4;
 					$$->expr = $2;
+					$$->location = @1;
 				}
 		;
 
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 57609e2d55c..34693c8a9e6 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -3289,20 +3289,20 @@ resolve_unique_index_expr(ParseState *pstate, InferClause *infer,
 					 errmsg("%s is not allowed in ON CONFLICT clause",
 							"ASC/DESC"),
 					 parser_errposition(pstate,
-										exprLocation((Node *) infer))));
+										exprLocation((Node *) ielem))));
 		if (ielem->nulls_ordering != SORTBY_NULLS_DEFAULT)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 					 errmsg("%s is not allowed in ON CONFLICT clause",
 							"NULLS FIRST/LAST"),
 					 parser_errposition(pstate,
-										exprLocation((Node *) infer))));
+										exprLocation((Node *) ielem))));
 		if (ielem->opclassopts)
 			ereport(ERROR,
 					errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 					errmsg("operator class options are not allowed in ON CONFLICT clause"),
 					parser_errposition(pstate,
-									   exprLocation((Node *) infer)));
+									   exprLocation((Node *) ielem)));
 
 		if (!ielem->expr)
 		{
@@ -3342,7 +3342,7 @@ resolve_unique_index_expr(ParseState *pstate, InferClause *infer,
 			pInfer->infercollid = InvalidOid;
 		else
 			pInfer->infercollid = LookupCollation(pstate, ielem->collation,
-												  exprLocation(pInfer->expr));
+												  exprLocation((Node *) ielem));
 
 		if (!ielem->opclass)
 			pInfer->inferopclass = InvalidOid;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2b7b084f216..535c4060dd4 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1883,6 +1883,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
 		int16		opt = source_idx->rd_indoption[keyno];
 
 		iparam = makeNode(IndexElem);
+		iparam->location = -1;
 
 		if (AttributeNumberIsValid(attnum))
 		{
@@ -1974,6 +1975,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
 											   keyno);
 
 		iparam = makeNode(IndexElem);
+		iparam->location = -1;
 
 		if (AttributeNumberIsValid(attnum))
 		{
@@ -2813,6 +2815,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 			iparam->opclassopts = NIL;
 			iparam->ordering = SORTBY_DEFAULT;
 			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			iparam->location = -1;
 			index->indexParams = lappend(index->indexParams, iparam);
 		}
 
@@ -2929,6 +2932,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 		iparam->collation = NIL;
 		iparam->opclass = NIL;
 		iparam->opclassopts = NIL;
+		iparam->location = -1;
 		index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
 	}
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0f..b15b937660f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -816,6 +816,7 @@ typedef struct IndexElem
 	List	   *opclassopts;	/* opclass-specific options, or NIL */
 	SortByDir	ordering;		/* ASC/DESC/default */
 	SortByNulls nulls_ordering; /* FIRST/LAST/default */
+	ParseLoc	location;		/* token location, or -1 if unknown */
 } IndexElem;
 
 /*
-- 
2.34.1

Reply via email to