From dae3ff2bf8e8a6a72ac4b732711dfbb18d93b743 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Fri, 17 Oct 2025 19:14:09 +0200 Subject: [PATCH] fix(tesseract): Prevent duplicate measures in some cases --- .../postgres/sql-generation.test.ts | 63 +++++++++++++++++++ .../aggregate_multiplied_subquery.rs | 4 ++ .../processors/keys_sub_query.rs | 23 ++++--- .../src/planner/query_properties.rs | 15 +++++ 4 files changed, 95 insertions(+), 10 deletions(-) diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts index af22e9044b3c9..479a87eec240e 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts @@ -588,6 +588,9 @@ describe('SQL Generation', () => { }, { join_path: 'visitors.visitor_checkins', includes: ['visitor_checkins_count', 'id_sum'] + }, { + join_path: 'visitors.visitor_checkins.cards', + includes: ['max_id'] }] }) @@ -643,6 +646,10 @@ describe('SQL Generation', () => { measures: { count: { type: 'count' + }, + max_id: { + type: 'max', + sql: 'id' } }, @@ -2314,6 +2321,62 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL }); }); + it('multiplied subquery measures', async () => { + await compiler.compile(); + + const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: [ + 'visitor_checkins.id_sum', + 'visitor_checkins.revenue_per_checkin', + 'visitors.visitor_revenue' + ], + dimensions: [ + 'visitors.source', + 'visitor_checkins.source' + ], + timeDimensions: [], + timezone: 'America/Los_Angeles' + }); + + console.log(query.buildSqlAndParams()); + + return dbRunner.testQuery(query.buildSqlAndParams()).then(res => { + console.log(JSON.stringify(res)); + expect(res).toEqual( + [ + { + visitors__source: null, + vc__source: null, + vc__id_sum: null, + vc__revenue_per_checkin: null, + visitors__visitor_revenue: null + }, + { + visitors__source: 'some', + vc__source: null, + vc__id_sum: '12', + vc__revenue_per_checkin: '75', + visitors__visitor_revenue: '300' + }, + { + visitors__source: 'google', + vc__source: null, + vc__id_sum: '6', + vc__revenue_per_checkin: null, + visitors__visitor_revenue: null + }, + { + visitors__source: 'some', + vc__source: 'google', + vc__id_sum: '3', + vc__revenue_per_checkin: '100', + visitors__visitor_revenue: '100' + } + ] + ); + }); + }); + it('having filter', async () => { await compiler.compile(); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/aggregate_multiplied_subquery.rs b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/aggregate_multiplied_subquery.rs index c3da3703cd271..ac15599cbbd5a 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/aggregate_multiplied_subquery.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/aggregate_multiplied_subquery.rs @@ -32,6 +32,10 @@ impl<'a> LogicalNodeProcessor<'a, AggregateMultipliedSubquery> context, )?; + if context.dimensions_query { + return Ok(keys_query); + } + let keys_query_alias = format!("keys"); let mut join_builder = diff --git a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/keys_sub_query.rs b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/keys_sub_query.rs index 322dbb19dd22f..b6c7eb115583a 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/keys_sub_query.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/keys_sub_query.rs @@ -72,17 +72,8 @@ impl<'a> LogicalNodeProcessor<'a, KeysSubQuery> for KeysSubQueryProcessor<'a> { &references_builder, &mut context_factory, )?; - for member in keys_subquery - .schema() - .all_dimensions() - .chain(keys_subquery.primary_keys_dimensions().iter()) - { + for member in keys_subquery.schema().all_dimensions() { let alias = member.alias(); - /* self.builder.process_calc_group( - member, - &mut context_factory, - &keys_subquery.filter().all_filters(), - )?; */ references_builder.resolve_references_for_member( member.clone(), &None, @@ -91,6 +82,18 @@ impl<'a> LogicalNodeProcessor<'a, KeysSubQuery> for KeysSubQueryProcessor<'a> { select_builder.add_projection_member(member, Some(alias)); } + if !context.dimensions_query { + for member in keys_subquery.primary_keys_dimensions().iter() { + let alias = member.alias(); + references_builder.resolve_references_for_member( + member.clone(), + &None, + context_factory.render_references_mut(), + )?; + select_builder.add_projection_member(member, Some(alias)); + } + } + select_builder.set_distinct(); select_builder.set_filter(filter); let res = Rc::new(select_builder.build(query_tools.clone(), context_factory)); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs index 86265a901d0eb..bba7d3a94043b 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs @@ -912,6 +912,21 @@ impl QueryProperties { } } } + result.multi_stage_measures = result + .multi_stage_measures + .into_iter() + .unique_by(|itm| itm.full_name()) + .collect(); + result.regular_measures = result + .regular_measures + .into_iter() + .unique_by(|itm| itm.full_name()) + .collect(); + result.multiplied_measures = result + .multiplied_measures + .into_iter() + .unique_by(|itm| itm.measure.full_name()) + .collect(); Ok(result) }