From 2b02afdef0aa1afae24355f748f29bac89207bb8 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Mon, 5 Oct 2026 11:46:21 -0400 Subject: [PATCH v1 3/5] Keep typmod and collation when simplifying COALESCE COALESCE can be replaced by its only surviving argument, but that argument may have another typmod or collation than the COALESCE had. Add a RelabelType in that case. --- src/backend/optimizer/util/clauses.c | 13 +++++++++---- src/test/regress/expected/collate.icu.utf8.out | 14 ++++++++++++++ src/test/regress/expected/predicate.out | 10 ++++++++++ src/test/regress/sql/collate.icu.utf8.sql | 10 ++++++++++ src/test/regress/sql/predicate.sql | 6 ++++++ 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 3e1f210652d..eb9d77560a2 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3838,8 +3838,6 @@ eval_const_expressions_mutator(Node *node, if (expr_is_nonnullable(context->root, (Expr *) e, NOTNULL_SOURCE_HASHTABLE)) { - if (newargs == NIL) - return e; /* first expr */ newargs = lappend(newargs, e); break; } @@ -3858,10 +3856,17 @@ eval_const_expressions_mutator(Node *node, /* * If there's exactly one surviving argument, we no longer - * need COALESCE at all: the result is that argument + * need COALESCE at all: the result is that argument. Relabel + * it if it lacks the COALESCE's typmod or collation. */ if (list_length(newargs) == 1) - return (Node *) linitial(newargs); + return applyRelabelType((Node *) linitial(newargs), + coalesceexpr->coalescetype, + exprTypmod(node), + coalesceexpr->coalescecollid, + COERCE_IMPLICIT_CAST, + -1, + false); newcoalesce = makeNode(CoalesceExpr); newcoalesce->coalescetype = coalesceexpr->coalescetype; diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out index 5ec848b82e4..40fd4678915 100644 --- a/src/test/regress/expected/collate.icu.utf8.out +++ b/src/test/regress/expected/collate.icu.utf8.out @@ -3860,6 +3860,20 @@ SELECT a, b FROM groupby_collation_t GROUP BY a, b ORDER BY a, b; (2 rows) DROP TABLE groupby_collation_t; +-- +-- A COALESCE that is simplified to one of its arguments must keep its own +-- collation. +-- +CREATE TABLE coalesce_collation_t (a text COLLATE "C" NOT NULL, b text); +INSERT INTO coalesce_collation_t VALUES ('foo'), ('FOO'), ('bar'); +SELECT count(*) FROM + (SELECT COALESCE(a, b COLLATE case_insensitive) FROM coalesce_collation_t GROUP BY 1) s; + count +------- + 2 +(1 row) + +DROP TABLE coalesce_collation_t; -- virtual generated columns CREATE TABLE t5 ( a int, diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out index feae77cb840..3f40bcac871 100644 --- a/src/test/regress/expected/predicate.out +++ b/src/test/regress/expected/predicate.out @@ -516,6 +516,16 @@ SELECT * FROM pred_tab WHERE COALESCE(a, b) > 1; Filter: (a > 1) (2 rows) +-- Ensure that the replacement keeps the typmod of the COALESCE +CREATE TABLE pred_tab_vc (v varchar(3) NOT NULL); +CREATE TABLE pred_tab_ctas AS SELECT COALESCE(v, 'longer') AS c FROM pred_tab_vc; +\d pred_tab_ctas + Table "public.pred_tab_ctas" + Column | Type | Collation | Nullable | Default +--------+-------------------+-----------+----------+--------- + c | character varying | | | + +DROP TABLE pred_tab_vc, pred_tab_ctas; -- -- Test detection of non-nullable expressions in predicates -- diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql index b50cf3f6c7c..4bcd0b0aa4c 100644 --- a/src/test/regress/sql/collate.icu.utf8.sql +++ b/src/test/regress/sql/collate.icu.utf8.sql @@ -1492,6 +1492,16 @@ SELECT a, b FROM groupby_collation_t GROUP BY a, b ORDER BY a, b; DROP TABLE groupby_collation_t; +-- +-- A COALESCE that is simplified to one of its arguments must keep its own +-- collation. +-- +CREATE TABLE coalesce_collation_t (a text COLLATE "C" NOT NULL, b text); +INSERT INTO coalesce_collation_t VALUES ('foo'), ('FOO'), ('bar'); +SELECT count(*) FROM + (SELECT COALESCE(a, b COLLATE case_insensitive) FROM coalesce_collation_t GROUP BY 1) s; +DROP TABLE coalesce_collation_t; + -- virtual generated columns CREATE TABLE t5 ( a int, diff --git a/src/test/regress/sql/predicate.sql b/src/test/regress/sql/predicate.sql index 0f92bb52435..5417a8078ba 100644 --- a/src/test/regress/sql/predicate.sql +++ b/src/test/regress/sql/predicate.sql @@ -259,6 +259,12 @@ SELECT * FROM pred_tab WHERE COALESCE(b, a, b*a) > 1; EXPLAIN (COSTS OFF) SELECT * FROM pred_tab WHERE COALESCE(a, b) > 1; +-- Ensure that the replacement keeps the typmod of the COALESCE +CREATE TABLE pred_tab_vc (v varchar(3) NOT NULL); +CREATE TABLE pred_tab_ctas AS SELECT COALESCE(v, 'longer') AS c FROM pred_tab_vc; +\d pred_tab_ctas +DROP TABLE pred_tab_vc, pred_tab_ctas; + -- -- Test detection of non-nullable expressions in predicates -- -- 2.37.1 (Apple Git-137.1)