Skip to content

Commit 6e5c98f

Browse files
authored
fix(tesseract): Per branch aliases in rollupLambda unions (cube-js#11184)
1 parent 49351e2 commit 6e5c98f

8 files changed

Lines changed: 311 additions & 87 deletions

File tree

rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/compiled_pre_aggregation.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,21 @@ pub struct PreAggregationJoin {
2020

2121
#[derive(Clone, Debug)]
2222
pub struct PreAggregationUnion {
23-
pub items: Vec<Rc<PreAggregationTable>>,
23+
pub items: Vec<PreAggregationUnionItem>,
24+
}
25+
26+
/// A single member rollup of a `rollupLambda` union, paired with the
27+
/// member symbols of *that* rollup. The lambda exposes the first member
28+
/// rollup's symbols, but each branch stores its columns under its own
29+
/// cube aliases (e.g. `requests_stream__tenant_id` vs `requests__tenant_id`),
30+
/// so the physical builder needs each branch's own symbols to read the
31+
/// right column while projecting the lambda's unified alias.
32+
#[derive(Clone, Debug)]
33+
pub struct PreAggregationUnionItem {
34+
pub table: Rc<PreAggregationTable>,
35+
pub measures: Vec<Rc<MemberSymbol>>,
36+
pub dimensions: Vec<Rc<MemberSymbol>>,
37+
pub time_dimensions: Vec<Rc<MemberSymbol>>,
2438
}
2539

2640
#[derive(Clone, Debug)]

rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,14 @@ impl PreAggregationOptimizer {
414414
let items = union
415415
.items
416416
.iter()
417-
.map(|t| {
418-
Rc::new(PreAggregationTable {
417+
.map(|item| PreAggregationUnionItem {
418+
table: Rc::new(PreAggregationTable {
419419
usage_index: Some(usage_index),
420-
..t.as_ref().clone()
421-
})
420+
..item.table.as_ref().clone()
421+
}),
422+
measures: item.measures.clone(),
423+
dimensions: item.dimensions.clone(),
424+
time_dimensions: item.time_dimensions.clone(),
422425
})
423426
.collect();
424427
Rc::new(PreAggregationSource::Union(PreAggregationUnion { items }))

rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/pre_aggregations_compiler.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::logical_plan::PreAggregationJoin;
77
use crate::logical_plan::PreAggregationJoinItem;
88
use crate::logical_plan::PreAggregationTable;
99
use crate::logical_plan::PreAggregationUnion;
10+
use crate::logical_plan::PreAggregationUnionItem;
1011
use crate::planner::join_hints::JoinHints;
1112
use crate::planner::multi_fact_join_groups::{MeasuresJoinHints, MultiFactJoinGroups};
1213
use crate::planner::planners::JoinPlanner;
@@ -302,13 +303,22 @@ impl PreAggregationsCompiler {
302303
for (i, rollup) in pre_aggrs_for_lambda.clone().iter().enumerate() {
303304
match rollup.source.as_ref() {
304305
PreAggregationSource::Single(table) => {
305-
sources.push(Rc::new(table.clone()));
306+
// Carry this branch's own symbols: each member rollup stores its
307+
// columns under its own cube aliases, while the lambda exposes the
308+
// first rollup's symbols. The physical builder maps lambda members
309+
// to each branch's stored column through these.
310+
sources.push(PreAggregationUnionItem {
311+
table: Rc::new(table.clone()),
312+
measures: rollup.measures.clone(),
313+
dimensions: rollup.dimensions.clone(),
314+
time_dimensions: rollup.time_dimensions.clone(),
315+
});
306316
}
307317
_ => {
308318
return Err(CubeError::user(format!("Rollup lambda can't be nested")));
309319
}
310320
}
311-
if i > 1 {
321+
if i >= 1 {
312322
Self::match_symbols(&rollup.measures, &pre_aggrs_for_lambda[0].measures)?;
313323
Self::match_symbols(&rollup.dimensions, &pre_aggrs_for_lambda[0].dimensions)?;
314324
Self::match_time_dimensions(

rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,10 @@ impl PrettyPrint for PreAggregation {
178178
result.println("Union:", &state);
179179
let state = state.new_level();
180180
for item in union.items.iter() {
181-
result.println(&format!("-{}.{}", item.cube_name, item.name), &state);
181+
result.println(
182+
&format!("-{}.{}", item.table.cube_name, item.table.name),
183+
&state,
184+
);
182185
}
183186
}
184187
}

rust/cube/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/processors/pre_aggregation.rs

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::physical_plan::{
1111
};
1212
use crate::physical_plan_builder::PhysicalPlanBuilder;
1313
use crate::planner::sql_templates::PlanSqlTemplates;
14+
use crate::planner::MemberSymbol;
1415
use crate::planner::SqlJoinCondition;
1516
use cubenativeutils::CubeError;
1617
use std::rc::Rc;
@@ -73,68 +74,110 @@ impl PreAggregationProcessor<'_> {
7374
Ok(from)
7475
}
7576

77+
/// Resolve the column a union branch stores a lambda member under. The
78+
/// lambda exposes the first member rollup's symbols, but each branch keeps
79+
/// its own cube aliases (e.g. `requests_stream__tenant_id` vs the lambda's
80+
/// `requests__tenant_id`).
81+
fn find_branch_member(
82+
lambda_member: &Rc<MemberSymbol>,
83+
branch_members: &[Rc<MemberSymbol>],
84+
branch_cube_name: &str,
85+
) -> Result<Rc<MemberSymbol>, CubeError> {
86+
if let Some(member) = branch_members
87+
.iter()
88+
.find(|m| m.full_name() == lambda_member.full_name())
89+
{
90+
return Ok(member.clone());
91+
}
92+
let short_name = lambda_member.name();
93+
if let Some(member) = branch_members
94+
.iter()
95+
.find(|m| m.name() == short_name && m.cube_name() == branch_cube_name)
96+
{
97+
return Ok(member.clone());
98+
}
99+
Err(CubeError::internal(format!(
100+
"Lambda pre-aggregation member '{}' has no match in union branch '{}'",
101+
lambda_member.full_name(),
102+
branch_cube_name
103+
)))
104+
}
105+
76106
fn make_pre_aggregation_union_source(
77107
&self,
78108
pre_aggregation: &PreAggregation,
79109
union: &PreAggregationUnion,
80110
) -> Result<Rc<From>, CubeError> {
81111
if union.items.len() == 1 {
82-
let table_source = self.make_pre_aggregation_table_source(&union.items[0])?;
112+
let table_source = self.make_pre_aggregation_table_source(&union.items[0].table)?;
83113
return Ok(From::new(FromSource::Single(table_source)));
84114
}
85115
let query_tools = self.builder.query_tools();
86116

87117
let mut union_sources = Vec::new();
88118
for item in union.items.iter() {
89-
let table_source = self.make_pre_aggregation_table_source(&item)?;
119+
let branch_cube_name = &item.table.cube_name;
120+
let table_source = self.make_pre_aggregation_table_source(&item.table)?;
90121
let from = From::new(FromSource::Single(table_source));
91122
let mut select_builder = SelectBuilder::new(from);
92123
for dim in pre_aggregation.dimensions().iter() {
93-
let name_in_table =
94-
PlanSqlTemplates::member_alias_name(&item.cube_alias, &dim.name(), &None);
95-
let alias = dim.alias();
124+
// Read this branch's stored column for the lambda dimension and
125+
// project it under the lambda's unified alias.
126+
let branch_dim = Self::find_branch_member(dim, &item.dimensions, branch_cube_name)?;
96127
select_builder.add_projection_reference_member(
97128
&dim,
98-
QualifiedColumnName::new(None, name_in_table),
99-
Some(alias),
129+
QualifiedColumnName::new(None, branch_dim.alias()),
130+
Some(dim.alias()),
100131
);
101132
}
133+
// Match time dimensions on their base member so the granularity
134+
// suffix is applied consistently on both the read and output sides.
135+
let branch_time_bases = item
136+
.time_dimensions
137+
.iter()
138+
.map(|td| {
139+
if let Ok(t) = td.as_time_dimension() {
140+
t.base_symbol().clone()
141+
} else {
142+
td.clone()
143+
}
144+
})
145+
.collect::<Vec<_>>();
102146
for dim in pre_aggregation.time_dimensions().iter() {
103-
let (alias, granularity) = if let Ok(td) = dim.as_time_dimension() {
104-
(td.base_symbol().alias(), td.granularity().clone())
147+
let (lambda_base, granularity) = if let Ok(td) = dim.as_time_dimension() {
148+
(td.base_symbol().clone(), td.granularity().clone())
105149
} else {
106-
(dim.alias(), None)
150+
(dim.clone(), None)
107151
};
108152

109-
let name_in_table = PlanSqlTemplates::member_alias_name(
110-
&item.cube_alias,
111-
&dim.name(),
112-
&granularity,
113-
);
153+
let branch_base =
154+
Self::find_branch_member(&lambda_base, &branch_time_bases, branch_cube_name)?;
114155

115-
let suffix = if let Some(granularity) = granularity {
116-
format!("_{}", granularity.clone())
156+
let read_suffix = if let Some(granularity) = &granularity {
157+
format!("_{}", granularity)
158+
} else {
159+
String::new()
160+
};
161+
let name_in_table = format!("{}{}", branch_base.alias(), read_suffix);
162+
163+
let out_suffix = if let Some(granularity) = &granularity {
164+
format!("_{}", granularity)
117165
} else {
118166
"_day".to_string()
119167
};
120-
let alias = format!("{}{}", alias, suffix);
168+
let alias = format!("{}{}", lambda_base.alias(), out_suffix);
121169
select_builder.add_projection_reference_member(
122170
&dim,
123-
QualifiedColumnName::new(None, name_in_table.clone()),
171+
QualifiedColumnName::new(None, name_in_table),
124172
Some(alias),
125173
);
126174
}
127175
for meas in pre_aggregation.measures().iter() {
128-
let name_in_table = PlanSqlTemplates::member_alias_name(
129-
&item.cube_alias,
130-
&meas.name(),
131-
&meas.alias_suffix(),
132-
);
133-
let alias = meas.alias();
176+
let branch_meas = Self::find_branch_member(meas, &item.measures, branch_cube_name)?;
134177
select_builder.add_projection_reference_member(
135178
&meas,
136-
QualifiedColumnName::new(None, name_in_table.clone()),
137-
Some(alias),
179+
QualifiedColumnName::new(None, branch_meas.alias()),
180+
Some(meas.alias()),
138181
);
139182
}
140183
let context = SqlNodesFactory::new();

0 commit comments

Comments
 (0)