Skip to content

fix(tesseract): Emit GROUP BY for non-windowed Aggregate stages in ungrouped queries#11328

Open
esrever10 wants to merge 1 commit into
cube-js:masterfrom
esrever10:fix/tesseract-ungrouped-multi-stage-group-by
Open

fix(tesseract): Emit GROUP BY for non-windowed Aggregate stages in ungrouped queries#11328
esrever10 wants to merge 1 commit into
cube-js:masterfrom
esrever10:fix/tesseract-ungrouped-multi-stage-group-by

Conversation

@esrever10

Copy link
Copy Markdown

Check List

  • Tests have been run in packages where changes have been made if available
  • Linter has been run for changed code
  • Tests for the changes have been added if not covered yet
  • Docs have been added / updated if required

Issue Reference this PR resolves

Fixes #11327

Description of Changes Made

An ungrouped SQL API query (bare SELECT without GROUP BY, the shape BI tools emit when browsing a table) selecting a group_by/reduce_by-locked multi-stage measure plus any dimension produced invalid SQL: the measure's Aggregate stage projected plain sum(...) next to bare dimension columns with no GROUP BY, so databases enforcing SQL grouping rules (e.g. Postgres) rejected the statement. Full trace in #11327.

The fix follows from what the stage renders: a non-windowed Aggregate stage projects plain aggregate functions by construction, so its select must be grouped by its projected dimensions regardless of the query-level ungrouped flag. Ungrouped semantics are unaffected — the enclosing FullKeyAggregate join broadcasts the stage output back to detail rows. Rank / Calculate / windowed stages are untouched: they render window functions or plain expressions and are valid without GROUP BY. ORDER BY behavior is unchanged.

Changes:

  • physical_plan_builder/processors/multi_stage_measure_calculation.rs: emit GROUP BY when the stage is grouped or renders plain aggregates (calculation_type == Aggregate && window_function_to_use == None).
  • test_fixtures/.../integration_multi_stage.yaml: add unique_customers_group_by_status — a group_by-locked sum over count_distinct, i.e. a non-sum-rollable input that is not window-path eligible and therefore hits the plain-aggregate JOIN model (existing ungrouped tests only covered Calculate-type / window-path measures, which are valid without GROUP BY).
  • tests/integration/multi_stage/ungrouped.rs: regression test — fails on master (assert sql.contains("GROUP BY"); the whole statement previously had none), passes with the fix; also executes against Postgres under the integration-postgres feature.

Verification: cargo test -p cubesqlplanner — 1076 passed, 0 failed (new test red on master, green with the fix).

…grouped queries

An ungrouped SQL API query (bare SELECT without GROUP BY, as BI tools
send when browsing a table) selecting a multi-stage measure with a
group_by/reduce_by lock made the query-level ungrouped flag suppress
the GROUP BY of the measure's Aggregate stage, while the stage still
projected plain aggregate functions next to bare dimension columns.
Databases enforcing SQL grouping rules reject the statement:

  ERROR: column "fk_aggregate_keys.orders__city" must appear in the
  GROUP BY clause or be used in an aggregate function

A non-windowed Aggregate stage always renders plain aggregates, so it
must be grouped by its projected dimensions regardless of the query's
ungrouped flag; the enclosing FullKeyAggregate join broadcasts the
stage output back to detail rows, preserving ungrouped semantics.
Rank / Calculate / windowed stages are untouched — they render window
functions or plain expressions and are valid without GROUP BY.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:community Contribution from Cube.js community members. rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL API: ungrouped query selecting a group_by-locked multi-stage measure generates aggregates without GROUP BY (rejected by Postgres)

1 participant