On Tue, Aug 18, 2026 at 9:54 AM Tender Wang <[email protected]> wrote:
> On master, the qual is pushed below the DISTINCT:
>
> ```text
> Unique
>   ->  Sort
>         Sort Key: cit.t
>         ->  Seq Scan on cit
>               Filter: (CASE (t)::text WHEN 'A'::text THEN 1 ELSE 0 END = 1)
> ```
>
> This is suspicious because DISTINCT compares `t` using citext
> equality, under which `'a'` and `'A'` are equal, while the CASE
> expression casts `t` to text and therefore distinguishes them.

Thanks for the report.  I looked into it and I think the root cause is
that grouping_conflict_walker() treats the arg of a simple CASE as an
operand of each WHEN comparison, but only applied the collation half
of the direct-operand check, on the assumption that the WHEN operator
is always the type-default "=" and thus matches the grouping eqop.
That is not true once the arg is relabeled.  In your case with "CASE
t::text WHEN 'A'", the WHEN compares with texteq while the grouping
uses citext_eq, so the qual should be rejected just as "t::text = 'A'"
already is.

I reviewed your patch.  IIUC, it fixes this by adding an
equality_ops_are_compatible() check per WHEN inside the CaseExpr
branch.  That works for the reported query, but I'd rather not go that
way, for a few reasons.

It duplicates the operand check that grouping_check_operand already
implements, so the two would have to be kept in sync.

It assumes each WHEN condition is a bare OpExpr (the Assert), which
the parser does not guarantee.  It also skips the
op_is_safe_index_member() gate, which is what makes the opfamily test
meaningful for the direct-operand form.

More generally, the problem is that the CaseExpr branch re-implements
how a direct operand is checked, and does so incompletely.

I think it'd be better to avoid this duplication.  So I'd like to take
the approach used elsewhere in planner for the same placeholder: while
walking the WHEN conditions, the walker binds a Var arg in the context
and resolves each CaseTestExpr to it.  The Var is then checked as each
WHEN uses it.  This is how eval_const_expressions() handles the
CaseTestExpr nodes.

Attached is the patch doing that.

- Richard

Attachment: v2-0001-Fix-qual-pushdown-past-grouping-through-simple-CA.patch
Description: Binary data

Reply via email to