Skip to content

Commit 745ae38

Browse files
authored
feat(cubesql): Aggregation over dimensions support (#7290)
1 parent f785d8c commit 745ae38

File tree

4 files changed

+58
-7
lines changed

4 files changed

+58
-7
lines changed

rust/cubesql/cubesql/src/compile/mod.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18520,7 +18520,7 @@ ORDER BY \"COUNT(count)\" DESC"
1852018520
init_logger();
1852118521

1852218522
let query_plan = convert_select_to_query_plan(
18523-
"SELECT COALESCE(customer_gender, 'N/A'), MIN(avgPrice) mp FROM (SELECT AVG(avgPrice) avgPrice, customer_gender FROM KibanaSampleDataEcommerce GROUP BY 2 LIMIT 1) a GROUP BY 1"
18523+
"SELECT COALESCE(customer_gender, 'N/A'), AVG(avgPrice) mp FROM KibanaSampleDataEcommerce a GROUP BY 1"
1852418524
.to_string(),
1852518525
DatabaseProtocol::PostgreSQL,
1852618526
)
@@ -18549,7 +18549,7 @@ ORDER BY \"COUNT(count)\" DESC"
1854918549
init_logger();
1855018550

1855118551
let query_plan = convert_select_to_query_plan(
18552-
"SELECT CASE WHEN customer_gender = 'female' THEN 'f' ELSE 'm' END, MIN(avgPrice) mp FROM (SELECT AVG(avgPrice) avgPrice, customer_gender FROM KibanaSampleDataEcommerce GROUP BY 2 LIMIT 1) a GROUP BY 1"
18552+
"SELECT CASE WHEN customer_gender = 'female' THEN 'f' ELSE 'm' END, AVG(avgPrice) mp FROM KibanaSampleDataEcommerce a GROUP BY 1"
1855318553
.to_string(),
1855418554
DatabaseProtocol::PostgreSQL,
1855518555
)
@@ -18666,7 +18666,7 @@ ORDER BY \"COUNT(count)\" DESC"
1866618666
init_logger();
1866718667

1866818668
let query_plan = convert_select_to_query_plan(
18669-
"SELECT * FROM (SELECT CASE WHEN customer_gender = 'female' THEN 'f' ELSE 'm' END, MIN(avgPrice) mp FROM (SELECT AVG(avgPrice) avgPrice, customer_gender FROM KibanaSampleDataEcommerce GROUP BY 2 LIMIT 1) a GROUP BY 1) q LIMIT 1123"
18669+
"SELECT * FROM (SELECT CASE WHEN customer_gender = 'female' THEN 'f' ELSE 'm' END, AVG(avgPrice) mp FROM KibanaSampleDataEcommerce a GROUP BY 1) q LIMIT 1123"
1867018670
.to_string(),
1867118671
DatabaseProtocol::PostgreSQL,
1867218672
)
@@ -18694,6 +18694,27 @@ ORDER BY \"COUNT(count)\" DESC"
1869418694
);
1869518695
}
1869618696

18697+
#[tokio::test]
18698+
async fn test_case_wrapper_ungrouped_on_dimension() {
18699+
if !Rewriter::sql_push_down_enabled() {
18700+
return;
18701+
}
18702+
init_logger();
18703+
18704+
let query_plan = convert_select_to_query_plan(
18705+
"SELECT CASE WHEN SUM(taxful_total_price) > 0 THEN SUM(taxful_total_price) ELSE 0 END FROM KibanaSampleDataEcommerce a"
18706+
.to_string(),
18707+
DatabaseProtocol::PostgreSQL,
18708+
)
18709+
.await;
18710+
18711+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
18712+
println!(
18713+
"Physical plan: {}",
18714+
displayable(physical_plan.as_ref()).indent()
18715+
);
18716+
}
18717+
1869718718
#[tokio::test]
1869818719
async fn test_wrapper_tableau_sunday_week() {
1869918720
if !Rewriter::sql_push_down_enabled() {

rust/cubesql/cubesql/src/compile/rewrite/converter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,7 @@ impl LanguageToLogicalPlanConverter {
17731773
LogicalPlanLanguage::CubeScan(_) => return true,
17741774
_ => (),
17751775
},
1776+
LogicalPlanLanguage::CubeScanWrapper(_) => return true,
17761777
_ => (),
17771778
}
17781779

rust/cubesql/cubesql/src/compile/rewrite/cost.rs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub struct BestCubePlan;
1212
/// - `filters` > `filter_members` - optimize for push down of filters
1313
/// - `filter_members` > `cube_members` - optimize for `inDateRange` filter push down to time dimension
1414
/// - `member_errors` > `cube_members` - extra cube members may be required (e.g. CASE)
15+
/// - `member_errors` > `wrapper_nodes` - use SQL push down where possible if cube scan can't be detected
1516
/// - match errors by priority - optimize for more specific errors
1617
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
1718
pub struct CubePlanCost {
@@ -21,13 +22,15 @@ pub struct CubePlanCost {
2122
filters: i64,
2223
structure_points: i64,
2324
filter_members: i64,
25+
empty_wrappers: i64,
2426
member_errors: i64,
27+
wrapper_nodes: i64,
28+
ast_size_outside_wrapper: usize,
2529
cube_members: i64,
2630
errors: i64,
27-
ast_size_outside_wrapper: usize,
28-
wrapper_nodes: i64,
2931
cube_scan_nodes: i64,
3032
ast_size: usize,
33+
ast_size_inside_wrapper: usize,
3134
}
3235

3336
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -99,11 +102,13 @@ impl CubePlanCost {
99102
cube_members: self.cube_members + other.cube_members,
100103
errors: self.errors + other.errors,
101104
structure_points: self.structure_points + other.structure_points,
105+
empty_wrappers: self.empty_wrappers + other.empty_wrappers,
102106
ast_size_outside_wrapper: self.ast_size_outside_wrapper
103107
+ other.ast_size_outside_wrapper,
104108
wrapper_nodes: self.wrapper_nodes + other.wrapper_nodes,
105109
cube_scan_nodes: self.cube_scan_nodes + other.cube_scan_nodes,
106110
ast_size: self.ast_size + other.ast_size,
111+
ast_size_inside_wrapper: self.ast_size_inside_wrapper + other.ast_size_inside_wrapper,
107112
}
108113
}
109114

@@ -112,7 +117,11 @@ impl CubePlanCost {
112117
replacers: self.replacers,
113118
table_scans: self.table_scans,
114119
filters: self.filters,
115-
non_detected_cube_scans: self.non_detected_cube_scans,
120+
non_detected_cube_scans: match state {
121+
CubePlanState::Wrapped => 0,
122+
CubePlanState::Unwrapped(_) => self.non_detected_cube_scans,
123+
CubePlanState::Wrapper => 0,
124+
},
116125
filter_members: self.filter_members,
117126
member_errors: self.member_errors,
118127
cube_members: self.cube_members,
@@ -123,9 +132,21 @@ impl CubePlanCost {
123132
CubePlanState::Unwrapped(size) => *size,
124133
CubePlanState::Wrapper => 0,
125134
} + self.ast_size_outside_wrapper,
135+
empty_wrappers: match state {
136+
CubePlanState::Wrapped => 0,
137+
CubePlanState::Unwrapped(_) => 0,
138+
CubePlanState::Wrapper => {
139+
if self.ast_size_inside_wrapper == 0 {
140+
1
141+
} else {
142+
0
143+
}
144+
}
145+
} + self.empty_wrappers,
126146
wrapper_nodes: self.wrapper_nodes,
127147
cube_scan_nodes: self.cube_scan_nodes,
128148
ast_size: self.ast_size,
149+
ast_size_inside_wrapper: self.ast_size_inside_wrapper,
129150
}
130151
}
131152
}
@@ -165,6 +186,11 @@ impl CostFunction<LogicalPlanLanguage> for BestCubePlan {
165186
_ => 0,
166187
};
167188

189+
let ast_size_inside_wrapper = match enode {
190+
LogicalPlanLanguage::WrappedSelect(_) => 1,
191+
_ => 0,
192+
};
193+
168194
let wrapper_nodes = match enode {
169195
LogicalPlanLanguage::CubeScanWrapper(_) => 1,
170196
_ => 0,
@@ -242,7 +268,9 @@ impl CostFunction<LogicalPlanLanguage> for BestCubePlan {
242268
errors: this_errors,
243269
structure_points,
244270
wrapper_nodes,
271+
empty_wrappers: 0,
245272
ast_size_outside_wrapper: 0,
273+
ast_size_inside_wrapper,
246274
cube_scan_nodes,
247275
ast_size: 1,
248276
},

rust/cubesql/cubesql/src/compile/rewrite/rewriter.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ impl Rewriter {
339339
vec![]
340340
};
341341
let extractor = Extractor::new(&runner.egraph, BestCubePlan);
342-
let (_, best) = extractor.find_best(root);
342+
let (best_cost, best) = extractor.find_best(root);
343343
let qtrace_best_graph = if Qtrace::is_enabled() {
344344
best.as_ref().iter().cloned().collect()
345345
} else {
@@ -354,6 +354,7 @@ impl Rewriter {
354354
.map(|(i, n)| format!("{}: {:?}", i, n))
355355
.join(", ")
356356
);
357+
log::debug!("Best cost: {:?}", best_cost);
357358
let converter =
358359
LanguageToLogicalPlanConverter::new(best, cube_context.clone(), auth_context);
359360
Ok((

0 commit comments

Comments
 (0)