-
Notifications
You must be signed in to change notification settings - Fork 2.1k
fix(schema-compiler): rollup_join cube hints from time dimensions #11784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| import { PostgresQuery } from '../../src/adapter/PostgresQuery'; | ||
| import { prepareYamlCompiler } from './PrepareCompiler'; | ||
|
|
||
| // `rollup_join` builds its join tree from cube hints collected off the | ||
| // pre-aggregation's own references. Those hints have to cover every cube the | ||
| // join is meant to span, including a cube whose single reference is its | ||
| // `time_dimension` - otherwise the join tree covers one cube, `targetJoins` | ||
| // comes back empty and the pre-aggregation is rejected with "Nothing to join in | ||
| // rollup join". | ||
| // | ||
| // The shape has no workaround: naming a dimension of that cube would put the | ||
| // hint in, but then that dimension has to be requested by every query for the | ||
| // rollup to match at all. | ||
| // | ||
| // Both planners build the pre-aggregation description here, so both are covered. | ||
|
Comment on lines
+3
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This 13-line header re-tells the PR description (the "no workaround" paragraph and the "both planners" note are commit-message material, not something a later editor needs in order to avoid reintroducing the bug). The load-bearing sentence is the first one; the rest can go. The two per-fixture one-liners below already say what each shape exercises. |
||
| const model = (joinedPreAggregation: string) => ` | ||
| cubes: | ||
| - name: locations | ||
| sql: > | ||
| SELECT 1 AS id, 'A' AS board_id, '2026-01-01'::timestamp AS ts UNION ALL | ||
| SELECT 2 AS id, 'A' AS board_id, '2026-01-02'::timestamp | ||
| joins: | ||
| - name: boards | ||
| sql: "{CUBE.board_id} = {boards.board_id}" | ||
| relationship: many_to_one | ||
| dimensions: | ||
| - name: id | ||
| sql: "{CUBE}.id" | ||
| type: number | ||
| primary_key: true | ||
| - name: board_id | ||
| sql: "{CUBE}.board_id" | ||
| type: string | ||
| - name: ts | ||
| sql: "{CUBE}.ts" | ||
| type: time | ||
| measures: | ||
| - name: count | ||
| type: count | ||
| pre_aggregations: | ||
| - name: locations_rollup | ||
| type: rollup | ||
| dimensions: | ||
| - board_id | ||
| time_dimension: ts | ||
| granularity: day | ||
|
|
||
| - name: boards | ||
| sql: > | ||
| SELECT 'A' AS board_id, 1 AS good | ||
| dimensions: | ||
| - name: board_id | ||
| sql: "{CUBE}.board_id" | ||
| type: string | ||
| primary_key: true | ||
| measures: | ||
| - name: good_count | ||
| sql: "{CUBE}.good" | ||
| type: sum | ||
| pre_aggregations: | ||
| - name: boards_rollup | ||
| type: rollup | ||
| measures: | ||
| - good_count | ||
| dimensions: | ||
| - board_id | ||
| ${joinedPreAggregation} | ||
| `; | ||
|
|
||
| // Reaches `locations` only through its time dimension. | ||
| const timeDimensionOnly = ` | ||
| - name: joined | ||
| type: rollup_join | ||
| rollups: | ||
| - locations.locations_rollup | ||
| - boards.boards_rollup | ||
| measures: | ||
| - good_count | ||
| time_dimension: locations.ts | ||
| granularity: day | ||
| `; | ||
|
|
||
| // Same, plus a dimension from `locations`. | ||
| const withAnchorDimension = ` | ||
| - name: joined | ||
| type: rollup_join | ||
| rollups: | ||
| - locations.locations_rollup | ||
| - boards.boards_rollup | ||
| measures: | ||
| - good_count | ||
| dimensions: | ||
| - locations.board_id | ||
| time_dimension: locations.ts | ||
| granularity: day | ||
| `; | ||
|
|
||
| const preAggregationSql = async (useNativeSqlPlanner: boolean, joinedPreAggregation: string, query: any) => { | ||
| const compilers = prepareYamlCompiler(model(joinedPreAggregation)); | ||
| await compilers.compiler.compile(); | ||
|
|
||
| return new PostgresQuery(compilers, { | ||
| timezone: 'UTC', | ||
| useNativeSqlPlanner, | ||
| ...query, | ||
| }) | ||
| .preAggregations.preAggregationsDescription() | ||
| .map((d: any) => d.loadSql?.[0] ?? '') | ||
| .join('\n'); | ||
| }; | ||
|
|
||
| describe.each([ | ||
| ['legacy', false], | ||
| ['tesseract', true], | ||
| ])('rollup_join cube hints (%s planner)', (_name, useNativeSqlPlanner) => { | ||
| const timeDimensions = [{ | ||
| dimension: 'locations.ts', | ||
| granularity: 'day', | ||
| dateRange: ['2026-01-01', '2026-01-31'], | ||
| }]; | ||
|
|
||
| it('builds the join when a cube is referenced only by time_dimension', async () => { | ||
| const sql = await preAggregationSql(useNativeSqlPlanner, timeDimensionOnly, { | ||
| measures: ['boards.good_count'], | ||
| timeDimensions, | ||
| }); | ||
|
|
||
| expect(sql).toContain('locations_locations_rollup'); | ||
|
Comment on lines
+118
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neither test covers the other consumer of Also, the assertions only check that both leg table names appear somewhere in the concatenated |
||
| expect(sql).toContain('boards_boards_rollup'); | ||
| }); | ||
|
|
||
| it('builds the join when a dimension of that cube is referenced too', async () => { | ||
| const sql = await preAggregationSql(useNativeSqlPlanner, withAnchorDimension, { | ||
| measures: ['boards.good_count'], | ||
| dimensions: ['locations.board_id'], | ||
| timeDimensions, | ||
| }); | ||
|
|
||
| expect(sql).toContain('locations_locations_rollup'); | ||
| expect(sql).toContain('boards_boards_rollup'); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| cubes: | ||
| - name: locations | ||
| sql: "SELECT 1 as id, 'A' as board_id, '2026-01-01'::timestamp as ts" | ||
|
|
||
| joins: | ||
| - name: boards | ||
| relationship: many_to_one | ||
| sql: "{CUBE.board_id} = {boards.board_id}" | ||
| dimensions: | ||
| - name: id | ||
| type: number | ||
| sql: id | ||
| primary_key: true | ||
| - name: board_id | ||
| type: string | ||
| sql: board_id | ||
| - name: ts | ||
| type: time | ||
| sql: ts | ||
| measures: | ||
| - name: count | ||
| type: count | ||
| pre_aggregations: | ||
| - name: locations_rollup | ||
| type: rollup | ||
| dimensions: | ||
| - board_id | ||
| time_dimension: ts | ||
| granularity: day | ||
|
|
||
| - name: boards | ||
| sql: "SELECT 'A' as board_id, 1 as good" | ||
|
|
||
| dimensions: | ||
| - name: board_id | ||
| type: string | ||
| sql: board_id | ||
| primary_key: true | ||
| measures: | ||
| - name: good_count | ||
| type: sum | ||
| sql: good | ||
| pre_aggregations: | ||
| - name: boards_rollup | ||
| type: rollup | ||
| measures: | ||
| - good_count | ||
| dimensions: | ||
| - board_id | ||
| # `locations` is reached only through its time dimension. | ||
| - name: joined | ||
| type: rollupJoin | ||
| measures: | ||
| - good_count | ||
| time_dimension: locations.ts | ||
| granularity: day | ||
| rollups: | ||
| - locations.locations_rollup | ||
| - boards.boards_rollup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
references.timeDimensionsis non-optional inPreAggregationReferences(CubeEvaluator.ts:152) andevaluatePreAggregationReferencesalways sets it; other call sites read it unguarded (e.g.mergePartitionTimeDimensionsatPreAggregations.ts:1388). The|| []is dead defensiveness that suggests the field can be missing.