From c664e035dacc5b161448c12866e0b5f5b4c0e01c Mon Sep 17 00:00:00 2001 From: Dhruv Chauhan 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