Skip to content

GROUP BY expression containing a placeholder does not match the same expression in the SELECT list #24042

Description

Describe the bug

If the same expression is written in both the SELECT list and the GROUP BY, and it
contains a placeholder, the two do not compare equal in the logical plan. The
grouping key is therefore not recognised as covering the SELECT expression, and any
column inside it is reported as ungrouped. Replacing the placeholder with a literal
makes the identical query plan fine.

The cause is that the two clauses are planned by different entry points.
sql_to_expr, used for the SELECT list, ends with a call to
Expr::infer_placeholder_types. The GROUP BY clause is planned by
sql_expr_to_logical_expr (sql/src/select.rs), which does not. So the projection
holds Placeholder { field: Some(..) } and the grouping key holds
Placeholder { field: None }, and Expr equality fails on the field.

HAVING, QUALIFY (both in select.rs) and ORDER BY (sql/src/expr/order_by.rs) are
planned the same way and have the same problem. DISTINCT ON and DISTRIBUTE BY also
skip inference, though nothing compares those against the SELECT list.

PREPARE is unaffected when the parameter type is declared, since the type is then
known at parse time in both positions.

To Reproduce

Against a table t(x bigint):

-- fails to plan
SELECT CASE WHEN x < $1 THEN 'low' ELSE 'high' END, count(*)
FROM t
GROUP BY CASE WHEN x < $1 THEN 'low' ELSE 'high' END;
Error during planning: Column in SELECT must be in GROUP BY or an aggregate function:
While expanding wildcard, column "t.x" must appear in the GROUP BY clause or must be
part of an aggregate function, currently only "CASE WHEN t.x < $1 THEN Utf8("low")
ELSE Utf8("high") END, count(Int64(1))" appears in the SELECT clause satisfies this
requirement

Note that the error lists the CASE as satisfying the requirement while still rejecting the column inside it.

Both of these plan without complaint:

-- literal instead of the placeholder
SELECT CASE WHEN x < 3 THEN 'low' ELSE 'high' END, count(*)
FROM t
GROUP BY CASE WHEN x < 3 THEN 'low' ELSE 'high' END;

-- placeholder, but with a declared type
PREPARE p(BIGINT) AS
SELECT CASE WHEN x < $1 THEN 'low' ELSE 'high' END, count(*)
FROM t
GROUP BY CASE WHEN x < $1 THEN 'low' ELSE 'high' END;

Reproduced on main.

Expected behavior

The placeholder version should plan the same as the literal version. Identical text in the SELECT list and the GROUP BY should produce one expression, so the CASE covers x and no further grouping is required.

Calling infer_placeholder_types on the planned GROUP BY, HAVING, QUALIFY and ORDER BY expressions, as sql_to_expr already does for the SELECT list, is enough to fix it.

Additional context

Somewhat vaguely similar to #19321 though that's related to typing and this is just a parsing bug.

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions