1mod and_predicate;
4mod cross_product;
5mod get_row_count;
6mod implicit_joins;
7mod index_join;
8mod index_selection;
9mod join_reorder;
10mod limit_skip_by_index;
11mod multi_column_or;
12mod not_simplification;
13mod order_by_index;
14mod outer_join_simplification;
15mod pass;
16mod predicate_pushdown;
17mod topn_pushdown;
18
19pub use and_predicate::AndPredicatePass;
20pub use cross_product::CrossProductPass;
21pub use get_row_count::{GetRowCountPass, GetRowCountPlan};
22pub use implicit_joins::ImplicitJoinsPass;
23pub use index_join::IndexJoinPass;
24pub use index_selection::IndexSelection;
25pub use join_reorder::JoinReorder;
26pub use limit_skip_by_index::LimitSkipByIndexPass;
27pub use multi_column_or::{MultiColumnOrConfig, MultiColumnOrPass};
28pub use not_simplification::NotSimplification;
29pub use order_by_index::OrderByIndexPass;
30pub use outer_join_simplification::OuterJoinSimplification;
31pub use pass::OptimizerPass;
32pub use predicate_pushdown::PredicatePushdown;
33pub use topn_pushdown::TopNPushdown;
34
35use crate::planner::{JoinAlgorithm, LogicalPlan, PhysicalPlan};
36use alloc::boxed::Box;
37use alloc::string::{String, ToString};
38use alloc::vec::Vec;
39use cynos_core::Value;
40
41pub struct Optimizer {
43 passes: Vec<Box<dyn OptimizerPass>>,
44}
45
46impl Default for Optimizer {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl Optimizer {
53 pub fn new() -> Self {
67 Self {
68 passes: alloc::vec![
69 Box::new(NotSimplification),
70 Box::new(AndPredicatePass),
71 Box::new(CrossProductPass),
72 Box::new(ImplicitJoinsPass),
73 Box::new(OuterJoinSimplification),
74 Box::new(PredicatePushdown),
75 Box::new(JoinReorder::new()),
76 ],
77 }
78 }
79
80 pub fn with_passes(passes: Vec<Box<dyn OptimizerPass>>) -> Self {
82 Self { passes }
83 }
84
85 pub fn optimize(&self, mut plan: LogicalPlan) -> LogicalPlan {
87 for pass in &self.passes {
88 plan = pass.optimize(plan);
89 }
90 plan
91 }
92
93 pub fn to_physical(&self, plan: LogicalPlan) -> PhysicalPlan {
96 let physical = self.logical_to_physical(plan);
97 TopNPushdown::new().optimize(physical)
99 }
100
101 fn logical_to_physical(&self, plan: LogicalPlan) -> PhysicalPlan {
102 match plan {
103 LogicalPlan::Scan { table } => PhysicalPlan::table_scan(table),
104
105 LogicalPlan::IndexScan {
106 table,
107 index,
108 range_start,
109 range_end,
110 include_start,
111 include_end,
112 } => PhysicalPlan::IndexScan {
113 table,
114 index,
115 range_start,
116 range_end,
117 include_start,
118 include_end,
119 limit: None,
120 offset: None,
121 reverse: false,
122 },
123
124 LogicalPlan::IndexGet { table, index, key } => {
125 PhysicalPlan::index_get(table, index, key)
126 }
127
128 LogicalPlan::IndexInGet { table, index, keys } => {
129 PhysicalPlan::index_in_get(table, index, keys)
130 }
131
132 LogicalPlan::GinIndexScan {
133 table,
134 index,
135 column: _,
136 column_index: _,
137 path,
138 value,
139 query_type,
140 } => {
141 let key = path.trim_start_matches("$.").to_string();
143
144 let value_str = value.map(|v| match v {
146 Value::String(s) => s,
147 Value::Int32(i) => alloc::format!("{}", i),
148 Value::Int64(i) => alloc::format!("{}", i),
149 Value::Float64(f) => alloc::format!("{}", f),
150 Value::Boolean(b) => alloc::format!("{}", b),
151 _ => alloc::format!("{:?}", v),
152 });
153
154 PhysicalPlan::gin_index_scan(table, index, key, value_str, query_type)
155 }
156
157 LogicalPlan::GinIndexScanMulti {
158 table,
159 index,
160 column: _,
161 pairs,
162 } => {
163 let string_pairs: Vec<(String, String)> = pairs
165 .into_iter()
166 .map(|(path, value)| {
167 let key = path.trim_start_matches("$.").to_string();
168 let value_str = match value {
169 Value::String(s) => s,
170 Value::Int32(i) => alloc::format!("{}", i),
171 Value::Int64(i) => alloc::format!("{}", i),
172 Value::Float64(f) => alloc::format!("{}", f),
173 Value::Boolean(b) => alloc::format!("{}", b),
174 _ => alloc::format!("{:?}", value),
175 };
176 (key, value_str)
177 })
178 .collect();
179
180 PhysicalPlan::gin_index_scan_multi(table, index, string_pairs)
181 }
182
183 LogicalPlan::Filter { input, predicate } => {
184 let input_physical = self.logical_to_physical(*input);
185 PhysicalPlan::filter(input_physical, predicate)
186 }
187
188 LogicalPlan::Project { input, columns } => {
189 let input_physical = self.logical_to_physical(*input);
190 PhysicalPlan::project(input_physical, columns)
191 }
192
193 LogicalPlan::Join {
194 left,
195 right,
196 condition,
197 join_type,
198 } => {
199 let left_physical = self.logical_to_physical(*left);
200 let right_physical = self.logical_to_physical(*right);
201
202 let algorithm = self.choose_join_algorithm(&condition);
204
205 match algorithm {
206 JoinAlgorithm::Hash => {
207 PhysicalPlan::hash_join(left_physical, right_physical, condition, join_type)
208 }
209 JoinAlgorithm::SortMerge => PhysicalPlan::sort_merge_join(
210 left_physical,
211 right_physical,
212 condition,
213 join_type,
214 ),
215 JoinAlgorithm::NestedLoop | JoinAlgorithm::IndexNestedLoop => {
216 PhysicalPlan::nested_loop_join(
217 left_physical,
218 right_physical,
219 condition,
220 join_type,
221 )
222 }
223 }
224 }
225
226 LogicalPlan::Aggregate {
227 input,
228 group_by,
229 aggregates,
230 } => {
231 let input_physical = self.logical_to_physical(*input);
232 PhysicalPlan::hash_aggregate(input_physical, group_by, aggregates)
233 }
234
235 LogicalPlan::Sort { input, order_by } => {
236 let input_physical = self.logical_to_physical(*input);
237 PhysicalPlan::sort(input_physical, order_by)
238 }
239
240 LogicalPlan::Limit {
241 input,
242 limit,
243 offset,
244 } => {
245 let input_physical = self.logical_to_physical(*input);
246 PhysicalPlan::limit(input_physical, limit, offset)
247 }
248
249 LogicalPlan::CrossProduct { left, right } => {
250 let left_physical = self.logical_to_physical(*left);
251 let right_physical = self.logical_to_physical(*right);
252 PhysicalPlan::CrossProduct {
253 left: Box::new(left_physical),
254 right: Box::new(right_physical),
255 }
256 }
257
258 LogicalPlan::Union { .. } => {
259 PhysicalPlan::Empty
261 }
262
263 LogicalPlan::Empty => PhysicalPlan::Empty,
264 }
265 }
266
267 fn choose_join_algorithm(&self, condition: &crate::ast::Expr) -> JoinAlgorithm {
268 if condition.is_equi_join() {
270 return JoinAlgorithm::Hash;
271 }
272
273 if condition.is_range_join() {
275 return JoinAlgorithm::NestedLoop;
276 }
277
278 JoinAlgorithm::NestedLoop
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::ast::Expr;
287
288 #[test]
289 fn test_optimizer_default() {
290 let optimizer = Optimizer::new();
291 assert_eq!(optimizer.passes.len(), 7);
292 }
293
294 #[test]
295 fn test_logical_to_physical_scan() {
296 let optimizer = Optimizer::new();
297 let logical = LogicalPlan::scan("users");
298 let physical = optimizer.to_physical(logical);
299
300 assert!(matches!(physical, PhysicalPlan::TableScan { table } if table == "users"));
301 }
302
303 #[test]
304 fn test_logical_to_physical_filter() {
305 let optimizer = Optimizer::new();
306 let logical = LogicalPlan::filter(
307 LogicalPlan::scan("users"),
308 Expr::eq(Expr::column("users", "id", 0), Expr::literal(1i64)),
309 );
310 let physical = optimizer.to_physical(logical);
311
312 assert!(matches!(physical, PhysicalPlan::Filter { .. }));
313 }
314
315 #[test]
316 fn test_logical_to_physical_join() {
317 let optimizer = Optimizer::new();
318 let logical = LogicalPlan::inner_join(
319 LogicalPlan::scan("a"),
320 LogicalPlan::scan("b"),
321 Expr::eq(Expr::column("a", "id", 0), Expr::column("b", "a_id", 0)),
322 );
323 let physical = optimizer.to_physical(logical);
324
325 assert!(matches!(physical, PhysicalPlan::HashJoin { .. }));
327 }
328}