* by collation-aware ancestors. At each GROUP Var with a nondeterministic
* varcollid, the clause has a conflict if any ancestor's inputcollid differs
* from the GROUP Var's varcollid. Most collation-aware nodes expose their
- * inputcollid through exprInputCollation(); RowCompareExpr is the exception,
- * as it carries one inputcollid per column in inputcollids[], so we descend
- * into its (largs[i], rargs[i]) pairs explicitly with the matching collation
- * pushed onto the stack.
+ * inputcollid through exprInputCollation(). Two structural exceptions need
+ * special handling:
+ *
+ * - RowCompareExpr carries one inputcollid per column in inputcollids[], so we
+ * descend into its (largs[i], rargs[i]) pairs explicitly with the matching
+ * collation pushed onto the stack.
+ *
+ * - A simple CASE (CaseExpr with a non-NULL arg) holds the arg outside the
+ * WHEN's OpExpr, even though the WHEN's OpExpr is the place where the
+ * comparison's inputcollid lives. Parse analysis builds each WHEN as
+ * "OpExpr(CaseTestExpr op val)" -- the CaseTestExpr is a placeholder for
+ * the arg. Before walking cexpr->arg we therefore push every WHEN's
+ * inputcollid onto the ancestor stack, so a GROUP Var at the arg is
+ * checked against the same collations the WHEN comparisons would apply.
+ * The WHEN bodies and defresult are then walked under the unchanged stack
+ * so their own collation contexts are picked up by the default path.
*/
static bool
having_collation_conflict_walker(Node *node, having_collation_ctx *ctx)
return false;
}
+ if (IsA(node, CaseExpr) && ((CaseExpr *) node)->arg != NULL)
+ {
+ CaseExpr *cexpr = (CaseExpr *) node;
+ int saved_len = list_length(ctx->ancestor_collids);
+ bool found;
+
+ /*
+ * Push every WHEN's inputcollid before walking cexpr->arg, since each
+ * WHEN implicitly compares the arg under that inputcollid.
+ */
+ foreach_node(CaseWhen, cw, cexpr->args)
+ {
+ Oid collid = exprInputCollation((Node *) cw->expr);
+
+ if (OidIsValid(collid))
+ ctx->ancestor_collids = lappend_oid(ctx->ancestor_collids,
+ collid);
+ }
+
+ found = having_collation_conflict_walker((Node *) cexpr->arg, ctx);
+
+ ctx->ancestor_collids = list_truncate(ctx->ancestor_collids,
+ saved_len);
+
+ if (found)
+ return true;
+
+ /*
+ * Walk the WHEN bodies and defresult under the unchanged ancestor
+ * stack; any inputcollids inside them are picked up by the default
+ * path.
+ */
+ foreach_node(CaseWhen, cw, cexpr->args)
+ {
+ if (having_collation_conflict_walker((Node *) cw->expr, ctx) ||
+ having_collation_conflict_walker((Node *) cw->result, ctx))
+ return true;
+ }
+ return having_collation_conflict_walker((Node *) cexpr->defresult,
+ ctx);
+ }
+
this_collid = exprInputCollation(node);
if (OidIsValid(this_collid))
ctx->ancestor_collids = lappend_oid(ctx->ancestor_collids,
abc | 2
(1 row)
+-- Negative: simple-CASE form with conflicting WHEN comparison collation
+EXPLAIN (COSTS OFF)
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_sensitive THEN true ELSE false END);
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ HashAggregate
+ Group Key: x
+ Filter: CASE x WHEN 'abc'::text COLLATE case_sensitive THEN true ELSE false END
+ -> Seq Scan on test3ci
+(4 rows)
+
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_sensitive THEN true ELSE false END);
+ x | count
+-----+-------
+ abc | 2
+(1 row)
+
+-- Positive: simple-CASE form with matching collation, safe to push
+EXPLAIN (COSTS OFF)
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_insensitive THEN true ELSE false END);
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
+ HashAggregate
+ Group Key: x
+ -> Seq Scan on test3ci
+ Filter: CASE x WHEN 'abc'::text COLLATE case_insensitive THEN true ELSE false END
+(4 rows)
+
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_insensitive THEN true ELSE false END);
+ x | count
+-----+-------
+ abc | 2
+(1 row)
+
+-- Negative: nested CASE with collation conflict
+EXPLAIN (COSTS OFF)
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE WHEN (CASE x WHEN 'abc' COLLATE case_sensitive THEN 1 ELSE 0 END) = 1 THEN true ELSE false END);
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------
+ HashAggregate
+ Group Key: x
+ Filter: CASE WHEN (CASE x WHEN 'abc'::text COLLATE case_sensitive THEN 1 ELSE 0 END = 1) THEN true ELSE false END
+ -> Seq Scan on test3ci
+(4 rows)
+
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE WHEN (CASE x WHEN 'abc' COLLATE case_sensitive THEN 1 ELSE 0 END) = 1 THEN true ELSE false END);
+ x | count
+-----+-------
+ abc | 2
+(1 row)
+
-- Positive: conflicting collation but no grouping expression reference
EXPLAIN (COSTS OFF)
SELECT x, count(*) FROM test3ci GROUP BY x HAVING current_setting('server_version') = 'abc' COLLATE case_sensitive;
SELECT x, count(*) FROM test3ci GROUP BY x HAVING ROW(x, 1) < ROW('ABC' COLLATE case_sensitive, 1) ORDER BY 1;
SELECT x, count(*) FROM test3ci GROUP BY x HAVING ROW(x, 1) < ROW('ABC' COLLATE case_sensitive, 1) ORDER BY 1;
+-- Negative: simple-CASE form with conflicting WHEN comparison collation
+EXPLAIN (COSTS OFF)
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_sensitive THEN true ELSE false END);
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_sensitive THEN true ELSE false END);
+
+-- Positive: simple-CASE form with matching collation, safe to push
+EXPLAIN (COSTS OFF)
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_insensitive THEN true ELSE false END);
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE x WHEN 'abc' COLLATE case_insensitive THEN true ELSE false END);
+
+-- Negative: nested CASE with collation conflict
+EXPLAIN (COSTS OFF)
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE WHEN (CASE x WHEN 'abc' COLLATE case_sensitive THEN 1 ELSE 0 END) = 1 THEN true ELSE false END);
+SELECT x, count(*) FROM test3ci GROUP BY x HAVING (CASE WHEN (CASE x WHEN 'abc' COLLATE case_sensitive THEN 1 ELSE 0 END) = 1 THEN true ELSE false END);
+
-- Positive: conflicting collation but no grouping expression reference
EXPLAIN (COSTS OFF)
SELECT x, count(*) FROM test3ci GROUP BY x HAVING current_setting('server_version') = 'abc' COLLATE case_sensitive;