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.
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 toExpr::infer_placeholder_types. The GROUP BY clause is planned bysql_expr_to_logical_expr(sql/src/select.rs), which does not. So the projectionholds
Placeholder { field: Some(..) }and the grouping key holdsPlaceholder { field: None }, andExprequality fails on thefield.HAVING, QUALIFY (both in
select.rs) and ORDER BY (sql/src/expr/order_by.rs) areplanned the same way and have the same problem. DISTINCT ON and DISTRIBUTE BY also
skip inference, though nothing compares those against the SELECT list.
PREPAREis unaffected when the parameter type is declared, since the type is thenknown at parse time in both positions.
To Reproduce
Against a table
t(x bigint):Note that the error lists the CASE as satisfying the requirement while still rejecting the column inside it.
Both of these plan without complaint:
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
xand no further grouping is required.Calling
infer_placeholder_typeson the planned GROUP BY, HAVING, QUALIFY and ORDER BY expressions, assql_to_expralready 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.