Skip to content

[CALCITE-7675] GROUP BY ALL and ORDER BY ALL should infer keys only from expressions that depend on the input - #5127

Open
tisyabhatia wants to merge 2 commits into
apache:mainfrom
tisyabhatia:calcite-group-order-by-all-exclude-literals
Open

[CALCITE-7675] GROUP BY ALL and ORDER BY ALL should infer keys only from expressions that depend on the input#5127
tisyabhatia wants to merge 2 commits into
apache:mainfrom
tisyabhatia:calcite-group-order-by-all-exclude-literals

Conversation

@tisyabhatia

@tisyabhatia tisyabhatia commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

GROUP BY ALL and ORDER BY ALL infer their keys from the SELECT list. This excludes from that inference any expression that does not depend on the input row: 42, 'x', 1 + 1, upper('x'), current_date, a dynamic parameter. A non-deterministic call such as RAND() varies per row and is retained, as in CALCITE-7697. Where no key remains, GROUP BY ALL becomes GROUP BY () and ORDER BY ALL generates no clause.

Behavior change

SELECT 'x', count(*) FROM emps WHERE FALSE GROUP BY ALL returns one row rather than none, so JdbcTest.testGroupByAllOverEmptyInput is updated. Both assertions were right for the rule in force when written; this PR changes the rule. GROUP BY ALL is consequently no longer exactly equivalent to writing the non-aggregate SELECT items by hand.

Why

The expansion copies expressions out of the SELECT list, and those synthesized keys are then resolved as ordinals. Out of range it throws; in range it silently picks a different SELECT item:

SELECT sal, 42 FROM emp ORDER BY ALL             -- Ordinal out of range
SELECT 2, deptno FROM emp ORDER BY ALL           -- sorts on deptno twice
SELECT deptno, 1, count(*) FROM emp GROUP BY ALL -- groups on deptno twice

Both ORDER BY cases are the default conformance, where isSortByOrdinal() is true. The expanded form is stored as a view or materialized-table definition, so the ambiguity outlives the query.

This follows BigQuery, which infers keys only from expressions that reference a name in the FROM clause and falls back to GROUP BY () when none remain. DuckDB matches; Spark and Snowflake keep constants, so it is a real divergence.

Alternative

The constant could instead be retained and synthesized keys marked so they are never resolved as ordinals. That preserves every result but leaves GROUP BY 42 in stored definitions. Working, and I can switch if preferred.

Notes

Not included: BigQuery also excludes expressions referencing only other inferred keys (SELECT x, x + 1 ... GROUP BY ALL groups by x alone) — separate issue.

Tests for both clauses in SqlValidatorTest, agg.iq and sort.iq; reference.md updated. CI green.

@mihaibudiu

Copy link
Copy Markdown
Contributor

I guess you did not read the comment I left on the issue

@tisyabhatia tisyabhatia changed the title [CALCITE-7676] Exclude constant literals from GROUP BY ALL / ORDER BY ALL keys [CALCITE-7675] Exclude constant literals from GROUP BY ALL / ORDER BY ALL keys Aug 3, 2026
@tisyabhatia
tisyabhatia force-pushed the calcite-group-order-by-all-exclude-literals branch from 7b178b7 to 5e4cae7 Compare August 3, 2026 17:03
…rom expressions that depend on the input

GROUP BY ALL made a grouping key of every non-aggregate SELECT item, and
ORDER BY ALL made a sort key of every SELECT item, including items that have the
same value in every row. Grouping by such an item does not
divide the input, and where the item is a bare integer literal the expansion
emits SQL that is read back as a select-list ordinal:

  SELECT deptno, 42, count(*) FROM emp GROUP BY ALL

expanded to GROUP BY EMP.DEPTNO, 42, which fails validation with "Ordinal out of
range" wherever SqlConformance.isGroupByOrdinal() is true, and which silently
resolves to a different SELECT item where the value is within the select list's
size. The expanded form is what is stored as a view or materialized-table
definition, so the ambiguity outlives the original query.

ORDER BY ALL has the same defect, and in the default conformance, because
isSortByOrdinal() is true there: "SELECT sal, 42 FROM emp ORDER BY ALL" fails,
and "SELECT 2, deptno FROM emp ORDER BY ALL" silently sorts on deptno twice.

Infer keys only from expressions that depend on the input row. An expression
that references no column and all of whose operators are deterministic is
excluded: 42, 'x', 1 + 1, upper('x'), current_date, and a dynamic parameter. A
call to a non-deterministic operator such as RAND() does vary from row to row
and is retained, which is the rule [CALCITE-7697] applies to window keys one
phase later. An unresolved function is retained, since its determinism is not
yet known.

This follows BigQuery, which infers keys only from "expressions that ...
reference a name from the FROM clause", and which specifies that "if the set of
inferred grouping keys is empty after exclusions are applied, all input rows are
considered a single group for aggregation".

Behavior change: where a constant was previously the only grouping key, no key
now remains and the query becomes a single-group aggregation. So

  SELECT 'x', count(*) FROM emps WHERE FALSE GROUP BY ALL

returns one row rather than none, and JdbcTest.testGroupByAllOverEmptyInput is
updated accordingly. A consequence is that GROUP BY ALL is not exactly
equivalent to writing the non-aggregate SELECT items out by hand.

Where no sort key remains, ORDER BY ALL now generates no ORDER BY clause; an
empty but non-null sort list would unparse with redundant parentheses.

Not included, and left for a separate issue: BigQuery also excludes an
expression that only references keys inferred from other SELECT items, so that
"SELECT x, x + 1 FROM t GROUP BY ALL" groups by x alone.

A sub-query is retained without being inspected. Its identifiers include table
names and aliases, which are not expressions in this sense; resolving them here
would be premature, and would wrongly reject a query whose alias happens to name
a niladic constant under a conformance that requires parentheses.

Two limits of the rule are worth stating. It inherits those of
SqlOperator.isDeterministic(), which reports a user-defined function as
deterministic unless it says otherwise, so a call to one with no column argument
is excluded. And the rewrite now calls makeNullaryCall, so a niladic constant
used without parentheses under a conformance that disallows it may report
handleUnresolvedFunction from the rewrite rather than from select-list
validation.
@tisyabhatia tisyabhatia changed the title [CALCITE-7675] Exclude constant literals from GROUP BY ALL / ORDER BY ALL keys [CALCITE-7675] GROUP BY ALL and ORDER BY ALL should infer keys only from expressions that depend on the input Aug 13, 2026
@tisyabhatia
tisyabhatia force-pushed the calcite-group-order-by-all-exclude-literals branch from 5e4cae7 to 98d5119 Compare August 13, 2026 20:04
@mihaibudiu

Copy link
Copy Markdown
Contributor

your commit title and PR title should match the issue title

+ "where false\n"
+ "group by all")
.returnsCount(0);
.returns("EXPR$0=x; EXPR$1=0\n");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one of these two tests must be wrong.
you wrote both of them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR changes the rule, which I should have led with. Under CALCITE-7594 'x' was a grouping key, so empty input produced no groups; here keys come only from expressions that depend on the input, so none remains and the query is a single-group aggregation. I'm following BigQuery / DuckDB, which infers keys only from expressions referencing a FROM name and treats an empty set like one group.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. We have to agree on the semantics of this query. It cannot be dictated by the implementation. On the contrary: the implementation has to produce the correct result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expansion is what gets stored as a view definition, so:
SELECT deptno, count(*) as cnt, sum(sal) AS sm, 42 AS constant FROM emp GROUP BY ALL

unparses to GROUP BY 42. And that would fail with "ordinal out of range." The in-range case is also invalid behavior because SELECT ... 2 AS constant ... GROUP BY ALL resolves to GROUP BY deptno, 2, and 2 resolves to COUNT(*) which is an aggregate and we cannot group by aggregates.

On the empty-input semantics, Calcite's behavior:
SELECT count(), sum(sal) FROM emp GROUP BY ALL -- 1 row
SELECT count(
), sum(sal), 42 as constant FROM emp GROUP BY ALL -- 0 rows

the first is what is already in CALCITE-7594. Adding a constant should not change the cardinality so that's why the inferred keyset should exclude it.

…expansion

The expansion of GROUP BY ALL is what is stored as a view or
materialized-table definition, so it is read back by a validator that cannot
know which keys the user wrote and which the expansion synthesized. Assert
that the expansion carries the same meaning on re-parse under a conformance
where an integer in GROUP BY is a select-list ordinal.

Covers both ways the previous behaviour broke: an out-of-range literal, which
failed to validate, and an in-range one, which designated a different SELECT
item.
@tisyabhatia
tisyabhatia force-pushed the calcite-group-order-by-all-exclude-literals branch from 4e24c84 to e49c393 Compare August 14, 2026 20:44
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants