From 7e01dfa69169264464cd89752dabb06505e42fcb Mon Sep 17 00:00:00 2001 From: Tender Wang Date: Mon, 17 Aug 2026 17:20:05 +0800 Subject: [PATCH] Fix case when --- contrib/citext/expected/citext_1.out | 39 ++++++++++++++++++++++++++++ contrib/citext/sql/citext.sql | 31 ++++++++++++++++++++++ src/backend/optimizer/util/clauses.c | 35 ++++++++++--------------- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out index c5e5f180f2b..d7851435e5e 100644 --- a/contrib/citext/expected/citext_1.out +++ b/contrib/citext/expected/citext_1.out @@ -2682,3 +2682,42 @@ SELECT 'a'::citext ~>=~ 'B'::varchar AS t; -- varchar wins. t (1 row) +-- +-- Check grouping conflicts in simple CASE expressions. +-- +CREATE TABLE citext_distinct_test (t citext); +INSERT INTO citext_distinct_test VALUES ('a'), ('A'), ('b'); +-- DISTINCT ON groups 'a' and 'A' using citext equality. The secondary +-- sort key makes 'A' the deterministic representative of that group. +-- The outer simple CASE compares using text equality, so the qual must +-- remain above the DISTINCT ON operation. +EXPLAIN (COSTS OFF) +SELECT * +FROM ( + SELECT DISTINCT ON (t) t + FROM citext_distinct_test + ORDER BY t, t::text COLLATE "C" +) d +WHERE CASE t::text WHEN 'a' THEN 1 ELSE 0 END = 1; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Subquery Scan on d + Filter: (CASE (d.t)::text WHEN 'a'::text THEN 1 ELSE 0 END = 1) + -> Unique + -> Sort + Sort Key: citext_distinct_test.t, citext_distinct_test.t COLLATE "C" USING < + -> Seq Scan on citext_distinct_test +(6 rows) + +SELECT * +FROM ( + SELECT DISTINCT ON (t) t + FROM citext_distinct_test + ORDER BY t, t::text COLLATE "C" +) d +WHERE CASE t::text WHEN 'a' THEN 1 ELSE 0 END = 1; + t +--- +(0 rows) + +DROP TABLE citext_distinct_test; diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql index aa1cf9abd5c..38185d890b1 100644 --- a/contrib/citext/sql/citext.sql +++ b/contrib/citext/sql/citext.sql @@ -807,3 +807,34 @@ SELECT 'B'::citext ~<=~ 'a'::varchar AS t; -- varchar wins. SELECT 'a'::citext ~>~ 'B'::varchar AS t; -- varchar wins. SELECT 'a'::citext ~>=~ 'B'::varchar AS t; -- varchar wins. + +-- +-- Check grouping conflicts in simple CASE expressions. +-- + +CREATE TABLE citext_distinct_test (t citext); + +INSERT INTO citext_distinct_test VALUES ('a'), ('A'), ('b'); + +-- DISTINCT ON groups 'a' and 'A' using citext equality. The secondary +-- sort key makes 'A' the deterministic representative of that group. +-- The outer simple CASE compares using text equality, so the qual must +-- remain above the DISTINCT ON operation. +EXPLAIN (COSTS OFF) +SELECT * +FROM ( + SELECT DISTINCT ON (t) t + FROM citext_distinct_test + ORDER BY t, t::text COLLATE "C" +) d +WHERE CASE t::text WHEN 'a' THEN 1 ELSE 0 END = 1; + +SELECT * +FROM ( + SELECT DISTINCT ON (t) t + FROM citext_distinct_test + ORDER BY t, t::text COLLATE "C" +) d +WHERE CASE t::text WHEN 'a' THEN 1 ELSE 0 END = 1; + +DROP TABLE citext_distinct_test; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index c92b0f3dbb4..d560e548730 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -6350,13 +6350,12 @@ expression_has_grouping_conflict(Node *expr, * btree/hash member and so is not treated as a comparison here. * * Comparison nodes are OpExpr/ScalarArrayOpExpr whose operator is a btree/hash - * member, and RowCompareExpr (one operator and collation per column). A - * simple CASE (CaseExpr with a non-NULL arg) is a comparison in disguise: + * member, and RowCompareExpr (one operator and collation per column). + * A simple CASE (CaseExpr with a non-NULL arg) is a comparison in disguise: * parse analysis builds each WHEN as "OpExpr(CaseTestExpr op val)", with the * CaseTestExpr standing in for the arg, so the arg is effectively an operand - * of each WHEN's comparison. Those WHEN operators are always the type-default - * "=", matching the grouping eqop, so only a collation conflict is possible - * there. + * of each WHEN's comparison. Check that operand against each WHEN operator + * just as for an ordinary comparison node. */ static bool grouping_conflict_walker(Node *node, grouping_walker_ctx *ctx) @@ -6434,24 +6433,18 @@ grouping_conflict_walker(Node *node, grouping_walker_ctx *ctx) if (arg && IsA(arg, Var)) { - Var *var = (Var *) arg; - - /* - * The arg is a grouping column compared by every WHEN. For a - * nondeterministic collation, reject if any WHEN applies a - * different collation. - */ - if (OidIsValid(ctx->get_eqop(var, ctx->cb_context)) && - OidIsValid(var->varcollid) && - !get_collation_isdeterministic(var->varcollid)) + foreach_node(CaseWhen, cw, cexpr->args) { - foreach_node(CaseWhen, cw, cexpr->args) - { - Oid collid = exprInputCollation((Node *) cw->expr); + OpExpr *opexpr; - if (OidIsValid(collid) && collid != var->varcollid) - return true; - } + Assert(IsA(cw->expr, OpExpr)); + opexpr = (OpExpr *) cw->expr; + + if (grouping_check_operand((Node *) cexpr->arg, + opexpr->opno, + opexpr->inputcollid, + ctx)) + return true; } } else if (grouping_conflict_walker((Node *) cexpr->arg, ctx)) -- 2.43.0