Skip to content

Commit a061bb5

Browse files
committed
feat(query): support aggregate common subplans in CSE
1 parent 66a717a commit a061bb5

3 files changed

Lines changed: 214 additions & 1 deletion

File tree

src/query/sql/src/planner/optimizer/optimizers/cse/analyze.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ mod tests {
159159
use databend_common_expression::TableDataType;
160160
use databend_common_expression::TableField;
161161
use databend_common_expression::TableSchema;
162+
use databend_common_expression::types::DataType;
162163
use databend_common_expression::types::NumberDataType;
163164
use databend_common_meta_app::schema::CatalogInfo;
164165
use databend_common_meta_app::schema::DatabaseType;
@@ -167,10 +168,19 @@ mod tests {
167168
use databend_common_meta_app::schema::TableMeta;
168169

169170
use super::*;
171+
use crate::ColumnBindingBuilder;
172+
use crate::Symbol;
173+
use crate::Visibility;
170174
use crate::planner::metadata::Metadata;
175+
use crate::plans::Aggregate;
176+
use crate::plans::AggregateFunction;
177+
use crate::plans::AggregateMode;
178+
use crate::plans::BoundColumnRef;
171179
use crate::plans::Join;
172180
use crate::plans::JoinType;
173181
use crate::plans::RelOperator;
182+
use crate::plans::ScalarExpr;
183+
use crate::plans::ScalarItem;
174184
use crate::plans::Scan;
175185

176186
#[derive(Debug)]
@@ -240,6 +250,48 @@ mod tests {
240250
})))
241251
}
242252

253+
fn column_expr(metadata: &Metadata, table_index: usize) -> ScalarExpr {
254+
let column = metadata.columns_by_table_index(table_index)[0].clone();
255+
BoundColumnRef {
256+
span: None,
257+
column: ColumnBindingBuilder::new(
258+
column.name(),
259+
column.index(),
260+
Box::new(column.data_type()),
261+
Visibility::Visible,
262+
)
263+
.table_index(Some(table_index))
264+
.build(),
265+
}
266+
.into()
267+
}
268+
269+
fn max_aggregate_expr(metadata: &Metadata, table_index: usize, output_index: Symbol) -> SExpr {
270+
SExpr::create_unary(
271+
Arc::new(RelOperator::Aggregate(Aggregate {
272+
mode: AggregateMode::Initial,
273+
group_items: vec![],
274+
aggregate_functions: vec![ScalarItem {
275+
scalar: ScalarExpr::AggregateFunction(AggregateFunction {
276+
span: None,
277+
func_name: "max".to_string(),
278+
distinct: false,
279+
params: vec![],
280+
args: vec![column_expr(metadata, table_index)],
281+
return_type: Box::new(DataType::Number(NumberDataType::UInt64)),
282+
sort_descs: vec![],
283+
display_name: "max(a)".to_string(),
284+
}),
285+
index: output_index,
286+
}],
287+
from_distinct: false,
288+
rank_limit: None,
289+
grouping_sets: None,
290+
})),
291+
Arc::new(scan_expr(metadata, table_index)),
292+
)
293+
}
294+
243295
fn cross_join_expr(left: SExpr, right: SExpr) -> SExpr {
244296
SExpr::create_binary(
245297
Arc::new(RelOperator::Join(Join {
@@ -313,4 +365,27 @@ mod tests {
313365
.all(|cte| matches!(cte.child(0).unwrap().plan(), RelOperator::Scan(_)))
314366
);
315367
}
368+
369+
#[test]
370+
fn test_analyze_common_subexpression_matches_identical_aggregates() {
371+
let mut metadata = Metadata::default();
372+
let t1 = fake_fuse_table(1, "t1");
373+
374+
let t1_left = add_table(&mut metadata, t1.clone());
375+
let t1_right = add_table(&mut metadata, t1);
376+
377+
let left = max_aggregate_expr(&metadata, t1_left, Symbol::new(10));
378+
let right = max_aggregate_expr(&metadata, t1_right, Symbol::new(11));
379+
let root = cross_join_expr(left, right);
380+
381+
let (replacements, materialized_ctes) =
382+
analyze_common_subexpression(&root, &mut metadata).unwrap();
383+
384+
assert_eq!(replacements.len(), 2);
385+
assert_eq!(materialized_ctes.len(), 1);
386+
assert!(matches!(
387+
materialized_ctes[0].child(0).unwrap().plan(),
388+
RelOperator::Aggregate(_)
389+
));
390+
}
316391
}

src/query/sql/src/planner/optimizer/optimizers/cse/table_signature.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,32 @@
1414

1515
use std::collections::HashMap;
1616

17+
use crate::ColumnBindingBuilder;
1718
use crate::ColumnEntry;
1819
use crate::IndexType;
20+
use crate::ScalarExpr;
21+
use crate::Symbol;
22+
use crate::Visibility;
1923
use crate::optimizer::ir::SExpr;
2024
use crate::planner::metadata::Metadata;
25+
use crate::plans::Aggregate;
2126
use crate::plans::Join;
2227
use crate::plans::JoinType;
2328
use crate::plans::RelOperator;
29+
use crate::plans::ScalarItem;
2430
use crate::plans::Scan;
31+
use crate::plans::VisitorMut;
32+
use crate::plans::walk_expr_mut;
2533

26-
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2735
pub struct TableSignature {
2836
pub tables: Vec<IndexType>,
37+
pub aggregate: Option<AggregateSignature>,
38+
}
39+
40+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
41+
pub struct AggregateSignature {
42+
pub aggregate: Aggregate,
2943
}
3044

3145
pub fn collect_table_signatures(
@@ -63,6 +77,7 @@ fn collect_table_signatures_rec(
6377
signature_to_exprs
6478
.entry(TableSignature {
6579
tables: tables.clone(),
80+
aggregate: None,
6681
})
6782
.or_default()
6883
.push((path.clone(), expr.clone()));
@@ -81,15 +96,120 @@ fn collect_table_signatures_rec(
8196
signature_to_exprs
8297
.entry(TableSignature {
8398
tables: tables.clone(),
99+
aggregate: None,
84100
})
85101
.or_default()
86102
.push((path.clone(), expr.clone()));
87103
Some(tables)
88104
}
105+
RelOperator::Aggregate(aggregate)
106+
if child_tables.len() == 1 && child_tables[0].is_some() =>
107+
{
108+
let tables = child_tables[0].clone().unwrap();
109+
if let Some(aggregate_signature) = aggregate_signature(aggregate, expr.child(0).ok()?) {
110+
signature_to_exprs
111+
.entry(TableSignature {
112+
tables: tables.clone(),
113+
aggregate: Some(aggregate_signature),
114+
})
115+
.or_default()
116+
.push((path.clone(), expr.clone()));
117+
}
118+
None
119+
}
89120
_ => None,
90121
}
91122
}
92123

124+
fn aggregate_signature(aggregate: &Aggregate, input: &SExpr) -> Option<AggregateSignature> {
125+
if aggregate.rank_limit.is_some() || aggregate.grouping_sets.is_some() {
126+
return None;
127+
}
128+
129+
let input_columns = input
130+
.derive_relational_prop()
131+
.ok()?
132+
.output_columns
133+
.iter()
134+
.copied()
135+
.enumerate()
136+
.map(|(position, column)| (column, Symbol::new(position)))
137+
.collect::<HashMap<_, _>>();
138+
139+
let mut aggregate = aggregate.clone();
140+
aggregate.group_items = normalize_scalar_items(&aggregate.group_items, &input_columns)?;
141+
aggregate.aggregate_functions =
142+
normalize_scalar_items(&aggregate.aggregate_functions, &input_columns)?;
143+
144+
Some(AggregateSignature { aggregate })
145+
}
146+
147+
fn normalize_scalar_items(
148+
items: &[ScalarItem],
149+
input_columns: &HashMap<Symbol, Symbol>,
150+
) -> Option<Vec<ScalarItem>> {
151+
items
152+
.iter()
153+
.enumerate()
154+
.map(|(position, item)| {
155+
Some(ScalarItem {
156+
scalar: normalize_scalar_expr(&item.scalar, input_columns)?,
157+
index: Symbol::new(position),
158+
})
159+
})
160+
.collect()
161+
}
162+
163+
fn normalize_scalar_expr(
164+
scalar: &ScalarExpr,
165+
input_columns: &HashMap<Symbol, Symbol>,
166+
) -> Option<ScalarExpr> {
167+
let mut scalar = scalar.clone();
168+
let mut visitor = NormalizeColumnVisitor { input_columns };
169+
visitor.visit(&mut scalar).ok()?;
170+
Some(scalar)
171+
}
172+
173+
struct NormalizeColumnVisitor<'a> {
174+
input_columns: &'a HashMap<Symbol, Symbol>,
175+
}
176+
177+
impl VisitorMut<'_> for NormalizeColumnVisitor<'_> {
178+
fn visit(&mut self, expr: &mut ScalarExpr) -> databend_common_exception::Result<()> {
179+
walk_expr_mut(self, expr)
180+
}
181+
182+
fn visit_bound_column_ref(
183+
&mut self,
184+
col: &mut crate::plans::BoundColumnRef,
185+
) -> databend_common_exception::Result<()> {
186+
let Some(normalized) = self.input_columns.get(&col.column.index) else {
187+
return Err(databend_common_exception::ErrorCode::Internal(
188+
"aggregate CSE column is not produced by input",
189+
));
190+
};
191+
col.column = ColumnBindingBuilder::new(
192+
normalized.to_string(),
193+
*normalized,
194+
col.column.data_type.clone(),
195+
Visibility::Visible,
196+
)
197+
.build();
198+
Ok(())
199+
}
200+
201+
fn visit_aggregate_function(
202+
&mut self,
203+
aggregate: &mut crate::plans::AggregateFunction,
204+
) -> databend_common_exception::Result<()> {
205+
aggregate.display_name.clear();
206+
for expr in aggregate.exprs_mut() {
207+
self.visit(expr)?;
208+
}
209+
Ok(())
210+
}
211+
}
212+
93213
fn scan_signature(scan: &Scan, metadata: &Metadata) -> Option<IndexType> {
94214
let has_internal_column = scan.columns.iter().any(|column_index| {
95215
let column = metadata.column(*column_index);

tests/sqllogictests/suites/mode/standalone/explain/common_subexpression_optimizer.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,3 +677,21 @@ drop table time_dim;
677677

678678
statement ok
679679
drop table store;
680+
681+
statement ok
682+
create or replace table cse_agg_t as
683+
select number as a, number % 3 as b
684+
from numbers(6);
685+
686+
query III rowsort
687+
select l.b, l.max_a, r.max_a
688+
from (select b, max(a) as max_a from cse_agg_t group by b) l
689+
join (select b, max(a) as max_a from cse_agg_t group by b) r
690+
on l.b = r.b;
691+
----
692+
0 3 3
693+
1 4 4
694+
2 5 5
695+
696+
statement ok
697+
drop table cse_agg_t;

0 commit comments

Comments
 (0)