Tom Lane <[email protected]> writes:
> Hmm, yeah, that's not great, although I'd argue that the real issue in
> this particular example is that we should treat the whole-row Var as
> being named "t" rather than being ignored. Still, installing a
> fallback of "expr" isn't a bad idea.
good idea! A whole-row Var now contributes the relation's name, and
"expr" remains as a fallback for expressions that still yield no text
(e.g. a constant that sanitizes away to nothing).
CREATE TABLE t (a int, b int);
CREATE INDEX ON t ((t IS NOT NULL)); -- index t_t_idx, column "t"
CREATE INDEX ON t ((','::text)); -- index t_expr_idx, column
"expr"
CREATE INDEX ON t ((a + b)); -- index t_a_b_idx, column "a_b"
Regression test updated. make check is green.
Patch is attached.
Thanks,
Dhruv
From c664e035dacc5b161448c12866e0b5f5b4c0e01c Mon Sep 17 00:00:00 2001
From: Dhruv Chauhan <[email protected]>
Date: Tue, 28 Jul 2026 13:53:12 +0530
Subject: [PATCH v2] Avoid empty generated names for expression index columns
When an index column is an expression, its name is generated from the
Vars, Consts, and function names found in the expression. A whole-row
Var was skipped, and an expression yielding no usable text at all (for
example a constant made up only of punctuation) produced an empty name
-- giving an empty column name and an odd index name such as "t__idx".
Use the relation's name for a whole-row Var, and fall back to "expr"
when nothing usable is found, so the generated column name is never
empty. Ordinary expressions are unaffected.
---
src/backend/commands/indexcmds.c | 26 ++++++++++++++++-----
src/test/regress/expected/create_index.out | 27 ++++++++++++++++++++++
src/test/regress/sql/create_index.sql | 21 +++++++++++++++++
3 files changed, 68 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f..0cd3b58e07 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2874,6 +2874,9 @@ ChooseIndexExpressionName(Relation rel, Node *indexExpr)
context.buf = &buf;
/* Walk the tree, stopping when we have enough text */
(void) ChooseIndexExpressionName_walker(indexExpr, &context);
+ /* Fall back to "expr" if the walk found nothing, to avoid an empty name */
+ if (buf.len == 0)
+ appendStringInfoString(&buf, "expr");
/* Ensure generated names are shorter than NAMEDATALEN */
nlen = pg_mbcliplen(buf.data, buf.len, NAMEDATALEN - 1);
buf.data[nlen] = '\0';
@@ -2891,19 +2894,30 @@ ChooseIndexExpressionName_walker(Node *node,
{
Var *var = (Var *) node;
TupleDesc tupdesc = RelationGetDescr(context->rel);
- Form_pg_attribute att;
+ const char *name;
/* Paranoia: ignore the Var if it looks fishy */
if (var->varno != 1 || var->varlevelsup != 0 ||
- var->varattno <= 0 || var->varattno > tupdesc->natts)
+ var->varattno < 0 || var->varattno > tupdesc->natts)
return false;
- att = TupleDescAttr(tupdesc, var->varattno - 1);
- if (att->attisdropped)
- return false; /* even more paranoia; shouldn't happen */
+
+ if (var->varattno == 0)
+ {
+ /* Whole-row reference: use the relation's name */
+ name = RelationGetRelationName(context->rel);
+ }
+ else
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, var->varattno - 1);
+
+ if (att->attisdropped)
+ return false; /* even more paranoia; shouldn't happen */
+ name = NameStr(att->attname);
+ }
if (context->buf->len > 0)
appendStringInfoChar(context->buf, '_');
- appendStringInfoString(context->buf, NameStr(att->attname));
+ appendStringInfoString(context->buf, name);
/* Done if we've already reached NAMEDATALEN */
return (context->buf->len >= NAMEDATALEN);
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 1145d57726..281c0f1355 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1648,6 +1648,33 @@ CREATE INDEX ON syscol_table (a) WHERE ctid >= '(1000,0)';
ERROR: index creation on system columns is not supported
DROP TABLE syscol_table;
--
+-- Generated index column names for expressions. A whole-row reference
+-- contributes the relation name; an expression that yields no usable text
+-- at all falls back to "expr", so the column name is never empty.
+--
+CREATE TABLE gen_exprname (a int, b int);
+-- whole-row reference uses the relation name
+CREATE INDEX ON gen_exprname ((gen_exprname IS NOT NULL));
+-- nothing usable to extract -> fall back to "expr"
+CREATE INDEX ON gen_exprname ((','::text));
+-- normal expression still gets a descriptive name
+CREATE INDEX ON gen_exprname ((a + b));
+SELECT c.relname AS index_name, a.attname AS column_name
+ FROM pg_class c
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum > 0
+ WHERE c.relkind = 'i'
+ AND c.relnamespace = 'public'::regnamespace
+ AND c.relname LIKE 'gen_exprname%'
+ ORDER BY c.relname, a.attnum;
+ index_name | column_name
+-------------------------------+--------------
+ gen_exprname_a_b_idx | a_b
+ gen_exprname_expr_idx | expr
+ gen_exprname_gen_exprname_idx | gen_exprname
+(3 rows)
+
+DROP TABLE gen_exprname;
+--
-- Tests for IS NULL/IS NOT NULL with b-tree indexes
--
CREATE TABLE onek_with_null AS SELECT unique1, unique2 FROM onek;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index 8e59f6bcd0..002b8a0743 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -649,6 +649,27 @@ CREATE INDEX ON syscol_table (a) WHERE ctid >= '(1000,0)';
DROP TABLE syscol_table;
+--
+-- Generated index column names for expressions. A whole-row reference
+-- contributes the relation name; an expression that yields no usable text
+-- at all falls back to "expr", so the column name is never empty.
+--
+CREATE TABLE gen_exprname (a int, b int);
+-- whole-row reference uses the relation name
+CREATE INDEX ON gen_exprname ((gen_exprname IS NOT NULL));
+-- nothing usable to extract -> fall back to "expr"
+CREATE INDEX ON gen_exprname ((','::text));
+-- normal expression still gets a descriptive name
+CREATE INDEX ON gen_exprname ((a + b));
+SELECT c.relname AS index_name, a.attname AS column_name
+ FROM pg_class c
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum > 0
+ WHERE c.relkind = 'i'
+ AND c.relnamespace = 'public'::regnamespace
+ AND c.relname LIKE 'gen_exprname%'
+ ORDER BY c.relname, a.attnum;
+DROP TABLE gen_exprname;
+
--
-- Tests for IS NULL/IS NOT NULL with b-tree indexes
--
--
2.34.1