1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
use std::collections::HashMap;
use glaredb_error::Result;
use super::OptimizeRule;
use crate::expr::Expression;
use crate::expr::column_expr::ColumnExpr;
use crate::logical::binder::bind_context::BindContext;
use crate::logical::binder::table_list::TableRef;
use crate::logical::logical_project::LogicalProject;
use crate::logical::operator::{LocationRequirement, LogicalNode, LogicalOperator, Node};
#[derive(Debug, Clone, Copy)]
pub struct CommonSubExpression;
impl OptimizeRule for CommonSubExpression {
fn optimize(
&mut self,
bind_context: &mut BindContext,
mut plan: LogicalOperator,
) -> Result<LogicalOperator> {
// Optimize base plan.
optimizer_inner(&mut plan, bind_context)?;
// TODO: Come up with a good pattern that satisfies lifetimes.
// // Optimize materializations.
// for mat in bind_context.iter_materializations_mut() {
// optimizer_inner(&mut mat.plan, bind_context)?;
// }
Ok(plan)
}
}
fn optimizer_inner(operator: &mut LogicalOperator, bind_context: &mut BindContext) -> Result<()> {
match operator {
LogicalOperator::Project(n) => optimize_operator(n, bind_context),
LogicalOperator::Aggregate(n) => optimize_operator(n, bind_context),
_ => {
// Everything else, just skip and go to children.
for child in operator.children_mut() {
optimizer_inner(child, bind_context)?;
}
Ok(())
}
}
}
fn optimize_operator<N>(operator: &mut Node<N>, bind_context: &mut BindContext) -> Result<()>
where
Node<N>: LogicalNode,
{
debug_assert_eq!(1, operator.children.len());
let mut extracted = HashMap::new();
// Extract expressions.
operator.for_each_expr(|expr| extract_expressions(expr, &mut extracted))?;
let has_common_subexprs = extracted.values().any(|&count| count > 1);
if !has_common_subexprs {
// Just walk this operator's child.
return optimizer_inner(&mut operator.children[0], bind_context);
}
// We have common subexpressions, create a new projection
// containing those expression
let table_ref = bind_context.new_ephemeral_table()?;
// Set of common expressions eligble to replace.
let mut cse_replacements: HashMap<Expression, Option<ColumnExpr>> = extracted
.into_iter()
.filter_map(|(expr, count)| {
if count > 1 {
Some((expr.clone(), None))
} else {
None
}
})
.collect();
// New projections, udpated as we replace.
let mut projections = Vec::new();
operator.for_each_expr_mut(|expr| {
replace_expressions(
expr,
table_ref,
bind_context,
&mut cse_replacements,
&mut projections,
)
})?;
// Pop the old child, we'll be inserting a new projection between the child
// and the current operator.
let mut child = operator.children.pop().expect("a single child");
// Optimize child before putting it on the project.
optimizer_inner(&mut child, bind_context)?;
// Update this operator's children with a new projection.
operator.children.push(LogicalOperator::Project(Node {
node: LogicalProject {
projections,
projection_table: table_ref,
},
location: LocationRequirement::Any,
estimated_cardinality: child.estimated_cardinality(),
children: vec![child],
}));
Ok(())
}
/// Replace common expressions with a column expression referencing the new
/// projection.
fn replace_expressions(
expr: &mut Expression,
proj_ref: TableRef,
bind_context: &mut BindContext,
cse_replacements: &mut HashMap<Expression, Option<ColumnExpr>>,
projections: &mut Vec<Expression>,
) -> Result<()> {
match cse_replacements.get_mut(expr) {
Some(col_expr) => {
// This is a common expression. Check if it's already part of the
// projectons.
match col_expr.as_mut() {
Some(col_expr) => {
// Expression already in projections, just update the
// current expression to the column.
*expr = Expression::from(col_expr.clone());
Ok(())
}
None => {
// Expression not in projection. Create a new column ref and
// add it to projections.
let col_idx = bind_context.push_column_for_table(
proj_ref,
"__generated_cse_col_ref_for_expr",
expr.datatype()?,
)?;
let new_col_expr = ColumnExpr {
reference: (proj_ref, col_idx).into(),
datatype: expr.datatype()?,
};
// Ensure we share the same column ref for other expression.
*col_expr = Some(new_col_expr.clone());
let orig = std::mem::replace(expr, Expression::from(new_col_expr));
projections.push(orig);
Ok(())
}
}
}
None => {
// Not considered a common expression. However if we're on a column
// expression, we'll need to update it to point to the new
// projection node (and push the old column expr to the projection
// list).
if let Expression::Column(col_expr) = expr {
let col_idx = bind_context.push_column_for_table(
proj_ref,
"__generated_cse_col_ref_for_column",
col_expr.datatype.clone(),
)?;
let new_col_expr = ColumnExpr {
reference: (proj_ref, col_idx).into(),
datatype: col_expr.datatype.clone(),
};
let orig = std::mem::replace(expr, Expression::from(new_col_expr.clone()));
// Ensure all other column expressions share the same updated
// expr.
cse_replacements.insert(orig.clone(), Some(new_col_expr));
projections.push(orig);
return Ok(());
}
// Now move through children.
expr.for_each_child_mut(|child| {
replace_expressions(child, proj_ref, bind_context, cse_replacements, projections)
})
}
}
}
/// Extracts non-trivial, sub-expressions (potentially including itself) into
/// the hashmap.
fn extract_expressions<'a>(
expr: &'a Expression,
extracted: &mut HashMap<&'a Expression, usize>,
) -> Result<()> {
if expr.is_volatile() {
// Nothing we should try to do for this expression.
return Ok(());
}
match expr {
Expression::Column(_) | Expression::Literal(_) => Ok(()), // Trivial.
Expression::Aggregate(_) | Expression::Window(_) => {
// Don't try to move the aggregate itself, just handle its children.
expr.for_each_child(|child| extract_expressions(child, extracted))
}
Expression::Case(_) => Ok(()), // Short circuit eval, just avoid for now.
Expression::Conjunction(_) => Ok(()), // Short circuit eval, just avoid for now.
Expression::Subquery(_) => Ok(()), // Shouldn't be reachable by this point.
Expression::Arith(_)
| Expression::Between(_)
| Expression::Cast(_)
| Expression::Comparison(_)
| Expression::Is(_)
| Expression::Negate(_)
| Expression::Unnest(_)
| Expression::GroupingSet(_)
| Expression::ScalarFunction(_) => {
// "Normal" expressions we can extract.
*extracted.entry(expr).or_insert(0) += 1;
// Walk children.
expr.for_each_child(|child| extract_expressions(child, extracted))
}
}
}