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