quill_sql/optimizer/
logical_optimizer.rs1use crate::error::QuillSQLResult;
2use crate::optimizer::rule::{EliminateLimit, MergeLimit, PushDownLimit};
3use crate::plan::logical_plan::LogicalPlan;
4use std::sync::Arc;
5
6pub trait LogicalOptimizerRule {
11 fn try_optimize(&self, plan: &LogicalPlan) -> QuillSQLResult<Option<LogicalPlan>>;
14
15 fn name(&self) -> &str;
17
18 fn apply_order(&self) -> Option<ApplyOrder> {
22 None
23 }
24}
25
26pub enum ApplyOrder {
27 TopDown,
28 BottomUp,
29}
30
31#[derive(Clone)]
32pub struct LogicalOptimizer {
33 pub rules: Vec<Arc<dyn LogicalOptimizerRule + Send + Sync>>,
35 pub max_passes: usize,
36}
37
38impl LogicalOptimizer {
39 pub fn new() -> Self {
40 let rules: Vec<Arc<dyn LogicalOptimizerRule + Sync + Send>> = vec![
41 Arc::new(EliminateLimit {}),
42 Arc::new(MergeLimit {}),
43 Arc::new(PushDownLimit {}),
44 ];
45
46 Self {
47 rules,
48 max_passes: 3,
49 }
50 }
51
52 pub fn with_rules(rules: Vec<Arc<dyn LogicalOptimizerRule + Send + Sync>>) -> Self {
53 Self {
54 rules,
55 max_passes: 3,
56 }
57 }
58
59 pub fn optimize(&self, plan: &LogicalPlan) -> QuillSQLResult<LogicalPlan> {
60 let mut new_plan = plan.clone();
61 let mut i = 0;
62 while i < self.max_passes {
63 for rule in &self.rules {
64 if let Some(optimized_plan) = self.optimize_recursively(rule, &new_plan)? {
65 new_plan = optimized_plan;
66 }
67 }
68
69 i += 1;
70 }
71 Ok(new_plan)
72 }
73
74 pub fn optimize_recursively(
75 &self,
76 rule: &Arc<dyn LogicalOptimizerRule + Send + Sync>,
77 plan: &LogicalPlan,
78 ) -> QuillSQLResult<Option<LogicalPlan>> {
79 match rule.apply_order() {
80 Some(order) => match order {
81 ApplyOrder::TopDown => {
82 let optimize_self_opt = rule.try_optimize(plan)?;
83 let optimize_inputs_opt = match &optimize_self_opt {
84 Some(optimized_plan) => self.optimize_inputs(rule, optimized_plan)?,
85 _ => self.optimize_inputs(rule, plan)?,
86 };
87 Ok(optimize_inputs_opt.or(optimize_self_opt))
88 }
89 ApplyOrder::BottomUp => {
90 let optimize_inputs_opt = self.optimize_inputs(rule, plan)?;
91 let optimize_self_opt = match &optimize_inputs_opt {
92 Some(optimized_plan) => rule.try_optimize(optimized_plan)?,
93 _ => rule.try_optimize(plan)?,
94 };
95 Ok(optimize_self_opt.or(optimize_inputs_opt))
96 }
97 },
98 _ => rule.try_optimize(plan),
99 }
100 }
101
102 fn optimize_inputs(
103 &self,
104 rule: &Arc<dyn LogicalOptimizerRule + Send + Sync>,
105 plan: &LogicalPlan,
106 ) -> QuillSQLResult<Option<LogicalPlan>> {
107 let inputs = plan.inputs();
108 let result = inputs
109 .iter()
110 .map(|sub_plan| self.optimize_recursively(rule, sub_plan))
111 .collect::<QuillSQLResult<Vec<_>>>()?;
112 if result.is_empty() || result.iter().all(|o| o.is_none()) {
113 return Ok(None);
114 }
115
116 let new_inputs = result
117 .into_iter()
118 .zip(inputs)
119 .map(|(new_plan, old_plan)| match new_plan {
120 Some(plan) => plan,
121 None => old_plan.clone(),
122 })
123 .collect::<Vec<_>>();
124 plan.with_new_inputs(&new_inputs).map(Some)
125 }
126}