From 7e01dfa69169264464cd89752dabb06505e42fcb Mon Sep 17 00:00:00 2001
From: Tender Wang <tndrwang@gmail.com>
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

