Error position support for ComputeIndexAttrs
hi.
Following the addition of error position support to ComputePartitionAttrs in
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=1e5e4efd02b614908cae62d9452528462d307224
Both partition keys and indexes support expressions and share a 32-column
limit, CREATE INDEX can be as complicated as PARTITION BY expression, and given
that ComputeIndexAttrs already contains 14 calls to ereport(ERROR, ...).
Adding error position support for ComputeIndexAttrs seems to make sense.
To achieve this, ComputeIndexAttrs must receive a ParseState. Since
ComputeIndexAttrs is nested under DefineIndex , DefineIndex must also have a
ParseState.
v1-0001: almost the same as [1]/messages/by-id/202512121327.f2zimsr6guso@alvherre.pgsql, the only difference is after
makeNode(IndexElem),
we should set the location to -1.
v1-0002: Error position support for ComputeIndexAttrs
[0]: https://git.postgresql.org/cgit/postgresql.git/commit/?id=1e5e4efd02b614908cae62d9452528462d307224
[1]: /messages/by-id/202512121327.f2zimsr6guso@alvherre.pgsql
Attachments:
v1-0001-add-Location-to-IndexElem.patchtext/x-patch; charset=UTF-8; name=v1-0001-add-Location-to-IndexElem.patchDownload
From 8c0f78631b5f8132e5319a30d8c2ff4db99a2dea Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 16 Dec 2025 14:22:14 +0800
Subject: [PATCH v1 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/
discussion: https://postgr.es/m/
---
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 375b40b29af..8ce0fb6ab37 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
v1-0002-Error-position-support-for-index-specifications.patchtext/x-patch; charset=US-ASCII; name=v1-0002-Error-position-support-for-index-specifications.patchDownload
From a54eef07e0a05d39938b3503ed5dc1b24d11fd54 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 16 Dec 2025 14:46:11 +0800
Subject: [PATCH v1 2/2] Error position support for index specifications
Add support for error position reporting for index specifications.
comitfest: https://commitfest.postgresql.org/patch/
discussion: https://postgr.es/m/
---
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 +
src/test/regress/expected/collate.out | 2 +
src/test/regress/expected/insert_conflict.out | 6 +-
.../regress/expected/sqljson_queryfuncs.out | 56 +++++++++++++++++
10 files changed, 121 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 d9cccb6ac18..832e8bfcbc1 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,
@@ -934,7 +937,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,
@@ -1518,7 +1522,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,
@@ -1867,7 +1872,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,
@@ -1952,12 +1958,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;
@@ -1975,7 +1983,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);
@@ -2016,7 +2025,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))));
}
}
@@ -2031,19 +2041,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;
@@ -2087,7 +2101,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
{
@@ -2095,7 +2110,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;
@@ -2163,7 +2179,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
@@ -2176,7 +2193,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);
@@ -2226,12 +2244,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 6b1a00ed477..6b92c4947ed 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.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/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
On Tue, Dec 16, 2025 at 12:51 PM jian he <jian.universality@gmail.com> wrote:
hi.
Following the addition of error position support to ComputePartitionAttrs in
[0], we can extend this feature to ComputeIndexAttrs.Both partition keys and indexes support expressions and share a 32-column
limit, CREATE INDEX can be as complicated as PARTITION BY expression, and given
that ComputeIndexAttrs already contains 14 calls to ereport(ERROR, ...).
Adding error position support for ComputeIndexAttrs seems to make sense.To achieve this, ComputeIndexAttrs must receive a ParseState. Since
ComputeIndexAttrs is nested under DefineIndex , DefineIndex must also have a
ParseState.v1-0001: almost the same as [1], the only difference is after
makeNode(IndexElem),
we should set the location to -1.
v1-0002: Error position support for ComputeIndexAttrs
+1, patch looks quite straightforward and pretty much reasonable to me.
Regards,
Amul
On Tue, Dec 16, 2025 at 9:01 PM Amul Sul <sulamul@gmail.com> 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.
Attachments:
v2-0002-Error-position-support-for-index-specifications.patchtext/x-patch; charset=US-ASCII; name=v2-0002-Error-position-support-for-index-specifications.patchDownload
From 2cbd83fd0cc6683a6cf0066cb8556639db939bfb Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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-KJK6tsGo5w@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
v2-0001-add-Location-to-IndexElem.patchtext/x-patch; charset=UTF-8; name=v2-0001-add-Location-to-IndexElem.patchDownload
From 6bd9a9a3b4dbb32ca01cc456ad5e5131c485b367 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
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-KJK6tsGo5w@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
On Wed, 31 Dec 2025 at 13:14, jian he <jian.universality@gmail.com> wrote:
On Tue, Dec 16, 2025 at 9:01 PM Amul Sul <sulamul@gmail.com> wrote:
+1, patch looks quite straightforward and pretty much reasonable to me.
Regards,
Amulhi.
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.diffsThe attached patch only adjusts collate.linux.utf8.out and
collate.windows.win1252.out, with no other changes.
Hi!
Nice catch, I see this patch is indeed an improvement.
For v2-0002, I was wondering if there is any further improvements
possible. Namely, if we can pass parse state in cases where v-0002
passes NULL.
1) So, DefineIndex in bootstrap (src/backend/bootstrap/bootparse.y) -
obviously there is no need to support error position for bootstrap
2) DefineIndex in commands/indexcmds.c L1524 (inside DefineIndex
actually) - We do not need to pass parse state here, because if any
trouble, we will elog(ERROR) earlier.
3) DefineIndex in commands/tablecmds.c L 1305 - also executed in
child partition cases.
4) DefineIndex inside ATAddIndex. It is triggered for patterns like
"alter table t add constraint c unique (ii);" - current patch set
missing support for them. I tried to pass pstate here, but no success,
because exprLocation returns -1 in ComputeIndexAttrs. Please see my
attempt attached. I guess this can be completed to get location
support, but I do not have any time today.
5) DefineIndex inside AttachPartitionEnsureIndexes - looks like we
do not need pstate here.
--
Best regards,
Kirill Reshke
Attachments:
patch.diffapplication/octet-stream; name=patch.diffDownload
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ef564e9e3bf..c77ab07bc72 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1957,14 +1957,14 @@ ComputeIndexAttrs(ParseState *pstate,
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" named in key does not exist",
- attribute->name)),
- parser_errposition(pstate, exprLocation((Node *) attribute)));
+ attribute->name),
+ parser_errposition(pstate, exprLocation((Node *) attribute))));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
- attribute->name)),
- parser_errposition(pstate, exprLocation((Node *) attribute)));
+ attribute->name),
+ parser_errposition(pstate, exprLocation((Node *) attribute))));
}
attform = (Form_pg_attribute) GETSTRUCT(atttuple);
indexInfo->ii_IndexAttrNumbers[attn] = attform->attnum;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 32be3d3ca75..5f58f594582 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -456,15 +456,15 @@ static void validateForeignKeyConstraint(char *conname,
Relation rel, Relation pkrel,
Oid pkindOid, Oid constraintOid, bool hasperiod);
static void CheckAlterTableIsSafe(Relation rel);
-static void ATController(AlterTableStmt *parsetree,
+static void ATController(ParseState *pstate, AlterTableStmt *parsetree,
Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
AlterTableUtilityContext *context);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode,
AlterTableUtilityContext *context);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+static void ATRewriteCatalogs(ParseState *pstate, List **wqueue, LOCKMODE lockmode,
AlterTableUtilityContext *context);
-static void ATExecCmd(List **wqueue, AlteredTableInfo *tab,
+static void ATExecCmd(ParseState *pstate, List **wqueue, AlteredTableInfo *tab,
AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
AlterTableUtilityContext *context);
static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
@@ -542,7 +542,7 @@ static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, LOCKMODE lockmode,
AlterTableUtilityContext *context);
static void verifyNotNullPKCompatible(HeapTuple tuple, const char *colname);
-static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATExecAddIndex(ParseState *pstate, AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
@@ -4529,7 +4529,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
* Some of the fields therein, such as the relid, are used here as well.
*/
void
-AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+AlterTable(ParseState *pstate, AlterTableStmt *stmt, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{
Relation rel;
@@ -4539,7 +4539,7 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
CheckAlterTableIsSafe(rel);
- ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
+ ATController(pstate, stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
}
/*
@@ -4567,7 +4567,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
EventTriggerAlterTableRelid(relid);
- ATController(NULL, rel, cmds, recurse, lockmode, NULL);
+ ATController(NULL, NULL, rel, cmds, recurse, lockmode, NULL);
}
/*
@@ -4870,7 +4870,7 @@ AlterTableGetLockLevel(List *cmds)
* when requested.
*/
static void
-ATController(AlterTableStmt *parsetree,
+ATController(ParseState *pstate, AlterTableStmt *parsetree,
Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{
@@ -4889,7 +4889,7 @@ ATController(AlterTableStmt *parsetree,
relation_close(rel, NoLock);
/* Phase 2: update system catalogs */
- ATRewriteCatalogs(&wqueue, lockmode, context);
+ ATRewriteCatalogs(pstate, &wqueue, lockmode, context);
/* Phase 3: scan/rewrite tables as needed, and run afterStmts */
ATRewriteTables(parsetree, &wqueue, lockmode, context);
@@ -5308,7 +5308,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* conflicts).
*/
static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+ATRewriteCatalogs(ParseState *pstate, List **wqueue, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{
ListCell *ltab;
@@ -5340,7 +5340,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
tab->rel = relation_open(tab->relid, NoLock);
foreach(lcmd, subcmds)
- ATExecCmd(wqueue, tab,
+ ATExecCmd(pstate, wqueue, tab,
lfirst_node(AlterTableCmd, lcmd),
lockmode, pass, context);
@@ -5382,7 +5382,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
* ATExecCmd: dispatch a subcommand to appropriate execution routine
*/
static void
-ATExecCmd(List **wqueue, AlteredTableInfo *tab,
+ATExecCmd(ParseState *pstate, List **wqueue, AlteredTableInfo *tab,
AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
AlterTableUtilityContext *context)
{
@@ -5454,11 +5454,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
NULL);
break;
case AT_AddIndex: /* ADD INDEX */
- address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
+ address = ATExecAddIndex(pstate, tab, rel, (IndexStmt *) cmd->def, false,
lockmode);
break;
case AT_ReAddIndex: /* ADD INDEX */
- address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
+ address = ATExecAddIndex(pstate, tab, rel, (IndexStmt *) cmd->def, true,
lockmode);
break;
case AT_ReAddStatistics: /* ADD STATISTICS */
@@ -9646,7 +9646,7 @@ verifyNotNullPKCompatible(HeapTuple tuple, const char *colname)
* Return value is the address of the new index.
*/
static ObjectAddress
-ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
+ATExecAddIndex(ParseState *pstate, AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
bool check_rights;
@@ -9667,7 +9667,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
/* suppress notices when rebuilding existing index */
quiet = is_rebuild;
- address = DefineIndex(NULL,
+ address = DefineIndex(pstate,
RelationGetRelid(rel),
stmt,
InvalidOid, /* no predefined OID */
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b611eab0550..1d07b1b0ce3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1317,7 +1317,7 @@ ProcessUtilitySlow(ParseState *pstate,
EventTriggerAlterTableRelid(relid);
/* ... and do it */
- AlterTable(atstmt, lockmode, &atcontext);
+ AlterTable(pstate, atstmt, lockmode, &atcontext);
/* done */
EventTriggerAlterTableEnd();
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index e9b0fab0767..d0ff75e938f 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -17,6 +17,7 @@
#include "access/htup.h"
#include "catalog/dependency.h"
#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
#include "nodes/parsenodes.h"
#include "storage/lock.h"
#include "utils/relcache.h"
@@ -34,7 +35,7 @@ extern void RemoveRelations(DropStmt *drop);
extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
-extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+extern void AlterTable(ParseState *pstate, AlterTableStmt *stmt, LOCKMODE lockmode,
AlterTableUtilityContext *context);
extern LOCKMODE AlterTableGetLockLevel(List *cmds);
On Wed, Dec 31, 2025 at 10:37 PM Kirill Reshke <reshkekirill@gmail.com> wrote:
4) DefineIndex inside ATAddIndex. It is triggered for patterns like
"alter table t add constraint c unique (ii);" - current patch set
missing support for them. I tried to pass pstate here, but no success,
because exprLocation returns -1 in ComputeIndexAttrs. Please see my
attempt attached. I guess this can be completed to get location
support, but I do not have any time today.
hi.
This will not work for ``ALTER TABLE t ADD CONSTRAINT c UNIQUE (ii);``.
In this case, gram.y produces a single Constraint node, and Constraint node
contain only one location field. However, a unique location is required for each
IndexElem node.
Simply assigning the same location value to all IndexElem nodes does not seem
worth the effort required to add support for this.
see transformIndexConstraint:
``
foreach(lc, constraint->keys)
{
/* OK, add it to the index definition */
iparam = makeNode(IndexElem);
........
iparam->location = -1;
index->indexParams = lappend(index->indexParams, iparam);
}
``
also
``ALTER TABLE t ADD CONSTRAINT c UNIQUE (ii);``
the Constraint.location is the location of the word "CONSTRAINT",
which is far from the IndexElem location we want to report.
jian he <jian.universality@gmail.com> writes:
This will not work for ``ALTER TABLE t ADD CONSTRAINT c UNIQUE (ii);``.
In this case, gram.y produces a single Constraint node, and Constraint node
contain only one location field. However, a unique location is required for each
IndexElem node.
Yeah, the actual problem is that the column name(s) in Constraint are
just a list of String nodes without per-name locations.
transformIndexConstraint throws some errors using constraint->location
that really ought to point at an individual column name, so there's
room for improvement there, as seen in this example from the
regression tests:
-- UNIQUE with a range column/PERIOD that isn't there:
CREATE TABLE temporal_rng3 (
id INTEGER,
CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS)
);
ERROR: column "valid_at" named in key does not exist
LINE 3: CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT O...
^
But this seems like material for a separate patch.
regards, tom lane
jian he <jian.universality@gmail.com> writes:
The attached patch only adjusts collate.linux.utf8.out and
collate.windows.win1252.out, with no other changes.
Pushed with minor adjustments -- mostly, I used indexelem->location
directly instead of going through exprLocation().
regards, tom lane