From dd6c20a197daac474f8c7fe099e42d94c529993d Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Fri, 6 Mar 2026 17:48:34 +0100 Subject: [PATCH 1/4] fix(cube): Issue with rollup pre-aggragations matching for views --- .../src/adapter/PreAggregations.ts | 8 +- ...e-aggregations-calculated-measures.test.ts | 209 +++++++++++++++++- 2 files changed, 209 insertions(+), 8 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts index 2982c3970f73d..16899de004b31 100644 --- a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts +++ b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts @@ -784,9 +784,15 @@ export class PreAggregations { const canUse = (( windowGranularityMatches(references) ) && ( + (references.rollups.length > 0 && R.all( (m: string) => references.measures.indexOf(m) !== -1, - references.rollups.length > 0 ? transformedQuery.leafMeasures : transformedQuery.leafMeasuresFullPaths, + transformedQuery.leafMeasures, + ) + ) + || R.all( + (m: string) => references.measures.indexOf(m) !== -1, + transformedQuery.leafMeasuresFullPaths, ) || (transformedQuery.isAdditive && R.all( m => backAliasMeasures.indexOf(m) !== -1, transformedQuery.measures, diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts index d5e9bde869db0..65dad0cac6507 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts @@ -1,13 +1,8 @@ -import { - getEnv, -} from '@cubejs-backend/shared'; -import R from 'ramda'; -import { UserError } from '../../../src/compiler/UserError'; import { PostgresQuery } from '../../../src/adapter/PostgresQuery'; -import { prepareJsCompiler } from '../../unit/PrepareCompiler'; +import { prepareJsCompiler, prepareCompiler } from '../../unit/PrepareCompiler'; import { dbRunner } from './PostgresDBRunner'; -describe('PreAggregationsMultiStage', () => { +describe('PreAggregationsCalulatedMeasures', () => { jest.setTimeout(200000); const { compiler, joinGraph, cubeEvaluator } = prepareJsCompiler(` @@ -136,9 +131,209 @@ describe('PreAggregationsMultiStage', () => { }) + cube('facts', { + sql: 'select * from visitor_checkins', + sqlAlias: 'f', + measures: { + count: { type: 'count' }, + total_cost: { sql: 'id', type: 'sum' }, + avg_cost: { sql: \`\${CUBE.total_cost} / \${CUBE.count}\`, type: 'number' }, + }, + dimensions: { + id: { type: 'number', sql: 'id', primaryKey: true }, + line_item_id: { type: 'number', sql: 'visitor_id' }, + day: { type: 'time', sql: 'created_at' }, + }, + preAggregations: { + facts_rollup: { + type: 'rollup', + measures: [CUBE.count, CUBE.total_cost, CUBE.avg_cost], + dimensions: [CUBE.line_item_id], + timeDimension: CUBE.day, + granularity: 'day', + } + } + }) + + cube('line_items', { + sql: 'select * from visitors', + sqlAlias: 'li', + joins: { + facts: { + relationship: 'one_to_many', + sql: \`\${CUBE.id} = \${facts.line_item_id}\` + }, + campaigns: { + relationship: 'many_to_one', + sql: \`\${CUBE.id} = \${campaigns.id}\` + } + }, + measures: { + count: { type: 'count' } + }, + dimensions: { + id: { type: 'number', sql: 'id', primaryKey: true }, + name: { type: 'string', sql: 'source' }, + }, + preAggregations: { + li_rollup: { + type: 'rollup', + dimensions: [CUBE.id, CUBE.name], + }, + combined_rollup_join: { + type: 'rollupJoin', + measures: [line_items.facts.count, line_items.facts.total_cost, line_items.facts.avg_cost], + dimensions: [CUBE.name, campaigns.campaign_name], + timeDimension: line_items.facts.day, + granularity: 'day', + rollups: [campaigns.campaigns_rollup, facts.facts_rollup, CUBE.li_rollup], + } + } + }) + + cube('campaigns', { + sql: "select 1 as id, 'camp1' as campaign_name", + sqlAlias: 'c', + measures: { + count: { type: 'count' } + }, + dimensions: { + id: { type: 'number', sql: 'id', primaryKey: true }, + campaign_name: { type: 'string', sql: 'campaign_name' }, + }, + preAggregations: { + campaigns_rollup: { + type: 'rollup', + dimensions: [CUBE.id, CUBE.campaign_name], + } + } + }) + + view('my_view', { + cubes: [ + { join_path: line_items.facts, includes: '*', prefix: true }, + { join_path: line_items, includes: '*', prefix: true }, + { join_path: line_items.campaigns, includes: '*', prefix: true }, + ] + }) `); + it('rollupJoin matching with additive measures through view', async () => { + await compiler.compile(); + + const query = new PostgresQuery( + { joinGraph, cubeEvaluator, compiler }, + { + measures: [ + 'my_view.facts_count', + 'my_view.facts_total_cost', + ], + timeDimensions: [{ + dimension: 'my_view.facts_day', + granularity: 'day', + }], + timezone: 'America/Los_Angeles', + preAggregationsSchema: '', + } + ); + + const matchedPreAgg = query.preAggregations?.findPreAggregationForQuery(); + + const sqlAndParams = query.buildSqlAndParams(); + expect(sqlAndParams[0]).toContain('campaigns_rollup'); + expect(sqlAndParams[0]).toContain('facts_rollup'); + expect(sqlAndParams[0]).toContain('li_rollup'); + expect(matchedPreAgg).toBeDefined(); + expect(matchedPreAgg?.preAggregationName).toEqual('combined_rollup_join'); + return dbRunner.evaluateQueryWithPreAggregations(query).then(res => { + expect(res).toEqual( + + [ + { + my_view__facts_day_day: '2017-01-02T00:00:00.000Z', + my_view__facts_count: '1', + my_view__facts_total_cost: '1' + }, + { + my_view__facts_day_day: '2017-01-03T00:00:00.000Z', + my_view__facts_count: '1', + my_view__facts_total_cost: '2' + }, + { + my_view__facts_day_day: '2017-01-04T00:00:00.000Z', + my_view__facts_count: '3', + my_view__facts_total_cost: '12' + }, + { + my_view__facts_day_day: '2017-01-05T00:00:00.000Z', + my_view__facts_count: '1', + my_view__facts_total_cost: '6' + }, + { + my_view__facts_day_day: null, + my_view__facts_count: null, + my_view__facts_total_cost: null + } + ] + + ); + }); + }); + + it('rollupJoin matching with calculated measures through view', async () => { + await compiler.compile(); + + const query = new PostgresQuery( + { joinGraph, cubeEvaluator, compiler }, + { + measures: [ + 'my_view.facts_avg_cost', + ], + timeDimensions: [{ + dimension: 'my_view.facts_day', + granularity: 'day', + }], + timezone: 'America/Los_Angeles', + preAggregationsSchema: '', + } + ); + + const matchedPreAgg = query.preAggregations?.findPreAggregationForQuery(); + + const sqlAndParams = query.buildSqlAndParams(); + expect(sqlAndParams[0]).toContain('campaigns_rollup'); + expect(sqlAndParams[0]).toContain('facts_rollup'); + expect(sqlAndParams[0]).toContain('li_rollup'); + expect(matchedPreAgg).toBeDefined(); + expect(matchedPreAgg?.preAggregationName).toEqual('combined_rollup_join'); + return dbRunner.evaluateQueryWithPreAggregations(query).then(res => { + expect(res).toEqual( + + [ + { + my_view__facts_day_day: '2017-01-02T00:00:00.000Z', + my_view__facts_avg_cost: '1.00000000000000000000' + }, + { + my_view__facts_day_day: '2017-01-03T00:00:00.000Z', + my_view__facts_avg_cost: '2.0000000000000000' + }, + { + my_view__facts_day_day: '2017-01-04T00:00:00.000Z', + my_view__facts_avg_cost: '4.0000000000000000' + }, + { + my_view__facts_day_day: '2017-01-05T00:00:00.000Z', + my_view__facts_avg_cost: '6.0000000000000000' + }, + { my_view__facts_day_day: null, my_view__facts_avg_cost: null } + ] + + ); + }); + }); + it('calculated measure pre-aggregation', () => compiler.compile().then(() => { const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, { measures: [ From 460e43b494f5b976421464264c19888d3931f04a Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Fri, 6 Mar 2026 17:50:55 +0100 Subject: [PATCH 2/4] in work --- packages/cubejs-schema-compiler/test/unit/base-query.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cubejs-schema-compiler/test/unit/base-query.test.ts b/packages/cubejs-schema-compiler/test/unit/base-query.test.ts index 5aacf5344dcff..1ae0df0b0c7d5 100644 --- a/packages/cubejs-schema-compiler/test/unit/base-query.test.ts +++ b/packages/cubejs-schema-compiler/test/unit/base-query.test.ts @@ -1012,7 +1012,7 @@ describe('SQL Generation', () => { relationship: 'one_to_one' }, }` - }).replace(`sql: \`\${CUBE}.location = 'San Francisco'\``, `sql: \`\${FILTER_PARAMS.cardsA.location.filter('location')}\``), + }).replace('sql: `${CUBE}.location = \'San Francisco\'`', 'sql: `${FILTER_PARAMS.cardsA.location.filter(\'location\')}`'), createCubeSchema({ name: 'cardsB', sqlTable: 'card2_tbl', From 7a5ee13c9b5fc2dee1b66713108261fcc227f5ee Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Fri, 6 Mar 2026 17:51:38 +0100 Subject: [PATCH 3/4] in work --- packages/cubejs-schema-compiler/test/unit/base-query.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cubejs-schema-compiler/test/unit/base-query.test.ts b/packages/cubejs-schema-compiler/test/unit/base-query.test.ts index 1ae0df0b0c7d5..5aacf5344dcff 100644 --- a/packages/cubejs-schema-compiler/test/unit/base-query.test.ts +++ b/packages/cubejs-schema-compiler/test/unit/base-query.test.ts @@ -1012,7 +1012,7 @@ describe('SQL Generation', () => { relationship: 'one_to_one' }, }` - }).replace('sql: `${CUBE}.location = \'San Francisco\'`', 'sql: `${FILTER_PARAMS.cardsA.location.filter(\'location\')}`'), + }).replace(`sql: \`\${CUBE}.location = 'San Francisco'\``, `sql: \`\${FILTER_PARAMS.cardsA.location.filter('location')}\``), createCubeSchema({ name: 'cardsB', sqlTable: 'card2_tbl', From 815c1c76d69050610fa5febb68cd0205b5fe8a18 Mon Sep 17 00:00:00 2001 From: Aleksandr Romanenko Date: Fri, 6 Mar 2026 18:28:24 +0100 Subject: [PATCH 4/4] additional test --- ...e-aggregations-calculated-measures.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts index 65dad0cac6507..18d38586c2087 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations-calculated-measures.test.ts @@ -281,6 +281,43 @@ describe('PreAggregationsCalulatedMeasures', () => { }); }); + it('rollupJoin matching with additive measures', async () => { + await compiler.compile(); + + const query = new PostgresQuery( + { joinGraph, cubeEvaluator, compiler }, + { + measures: [ + 'facts.count', + 'facts.total_cost', + ], + dimensions: ['line_items.name'], + timezone: 'America/Los_Angeles', + preAggregationsSchema: '', + } + ); + + const matchedPreAgg = query.preAggregations?.findPreAggregationForQuery(); + + const sqlAndParams = query.buildSqlAndParams(); + expect(sqlAndParams[0]).toContain('campaigns_rollup'); + expect(sqlAndParams[0]).toContain('facts_rollup'); + expect(sqlAndParams[0]).toContain('li_rollup'); + expect(matchedPreAgg).toBeDefined(); + expect(matchedPreAgg?.preAggregationName).toEqual('combined_rollup_join'); + return dbRunner.evaluateQueryWithPreAggregations(query).then(res => { + expect(res).toEqual( + + [ + { li__name: null, f__count: null, f__total_cost: null }, + { li__name: 'some', f__count: '5', f__total_cost: '15' }, + { li__name: 'google', f__count: '1', f__total_cost: '6' } + ] + + ); + }); + }); + it('rollupJoin matching with calculated measures through view', async () => { await compiler.compile();