1use crate::LogicalPlan;
12use sqlparser::dialect::GenericDialect;
13use sqlparser::parser::Parser;
14
15use super::OptPass;
16
17pub struct ConstantFolding;
21
22impl OptPass for ConstantFolding {
23 fn name(&self) -> &'static str {
24 "ConstantFolding"
25 }
26
27 fn apply(&self, plan: LogicalPlan) -> LogicalPlan {
28 fold_constants(plan)
29 }
30}
31
32pub(super) fn fold_constants(plan: LogicalPlan) -> LogicalPlan {
35 match plan {
36 LogicalPlan::Filter { input, predicate } => {
37 let inner = fold_constants(*input);
39
40 match evaluate_constant_predicate(&predicate) {
41 Some(true) => inner, Some(false) => LogicalPlan::Empty, None => LogicalPlan::Filter {
44 input: Box::new(inner),
45 predicate: fold_predicate_string(&predicate),
46 },
47 }
48 }
49 LogicalPlan::Project { input, columns } => LogicalPlan::Project {
50 input: Box::new(fold_constants(*input)),
51 columns,
52 },
53 LogicalPlan::Join {
54 left,
55 right,
56 on,
57 join_type,
58 algo_hint,
59 } => LogicalPlan::Join {
60 left: Box::new(fold_constants(*left)),
61 right: Box::new(fold_constants(*right)),
62 on,
63 join_type,
64 algo_hint,
65 },
66 LogicalPlan::Aggregate {
67 input,
68 group_by,
69 aggregates,
70 } => LogicalPlan::Aggregate {
71 input: Box::new(fold_constants(*input)),
72 group_by,
73 aggregates,
74 },
75 LogicalPlan::Sort { input, order_by } => LogicalPlan::Sort {
76 input: Box::new(fold_constants(*input)),
77 order_by,
78 },
79 LogicalPlan::Limit {
80 input,
81 count,
82 offset,
83 } => LogicalPlan::Limit {
84 input: Box::new(fold_constants(*input)),
85 count,
86 offset,
87 },
88 other => other,
89 }
90}
91
92fn evaluate_constant_predicate(predicate: &str) -> Option<bool> {
99 let dialect = GenericDialect {};
100 let mut parser = Parser::new(&dialect).try_with_sql(predicate).ok()?;
101 let expr = parser.parse_expr().ok()?;
102 eval_expr_bool(&expr)
103}
104
105fn eval_expr_bool(expr: &sqlparser::ast::Expr) -> Option<bool> {
108 use sqlparser::ast::{BinaryOperator, Expr, Value, ValueWithSpan};
109
110 match expr {
111 Expr::Value(ValueWithSpan {
113 value: Value::Boolean(b),
114 ..
115 }) => Some(*b),
116
117 Expr::BinaryOp { left, op, right } => {
119 if let (Some(lv), Some(rv)) = (expr_to_f64(left), expr_to_f64(right)) {
121 return Some(match op {
122 BinaryOperator::Eq => (lv - rv).abs() < f64::EPSILON,
123 BinaryOperator::NotEq => (lv - rv).abs() >= f64::EPSILON,
124 BinaryOperator::Lt => lv < rv,
125 BinaryOperator::LtEq => lv <= rv,
126 BinaryOperator::Gt => lv > rv,
127 BinaryOperator::GtEq => lv >= rv,
128 _ => return None,
129 });
130 }
131 if let (Some(ls), Some(rs)) = (expr_to_str(left), expr_to_str(right)) {
133 return Some(match op {
134 BinaryOperator::Eq => ls == rs,
135 BinaryOperator::NotEq => ls != rs,
136 _ => return None,
137 });
138 }
139 match op {
141 BinaryOperator::And => {
142 let lv = eval_expr_bool(left);
143 let rv = eval_expr_bool(right);
144 match (lv, rv) {
145 (Some(false), _) | (_, Some(false)) => Some(false),
146 (Some(true), Some(true)) => Some(true),
147 _ => None,
148 }
149 }
150 BinaryOperator::Or => {
151 let lv = eval_expr_bool(left);
152 let rv = eval_expr_bool(right);
153 match (lv, rv) {
154 (Some(true), _) | (_, Some(true)) => Some(true),
155 (Some(false), Some(false)) => Some(false),
156 _ => None,
157 }
158 }
159 _ => None,
160 }
161 }
162
163 Expr::Nested(inner) => eval_expr_bool(inner),
164 _ => None,
165 }
166}
167
168fn fold_predicate_string(predicate: &str) -> String {
175 let dialect = GenericDialect {};
176 let Ok(mut parser) = Parser::new(&dialect).try_with_sql(predicate) else {
177 return predicate.to_string();
178 };
179 let Ok(expr) = parser.parse_expr() else {
180 return predicate.to_string();
181 };
182 fold_expr(expr).to_string()
183}
184
185fn fold_expr(expr: sqlparser::ast::Expr) -> sqlparser::ast::Expr {
187 use sqlparser::ast::{BinaryOperator, Expr, Value, ValueWithSpan};
188
189 match expr {
190 Expr::BinaryOp { left, op, right } => {
191 let left = fold_expr(*left);
192 let right = fold_expr(*right);
193
194 if let (Some(lv), Some(rv)) = (expr_to_f64(&left), expr_to_f64(&right)) {
196 let result = match op {
197 BinaryOperator::Plus => Some(lv + rv),
198 BinaryOperator::Minus => Some(lv - rv),
199 BinaryOperator::Multiply => Some(lv * rv),
200 BinaryOperator::Divide if rv != 0.0 => Some(lv / rv),
201 _ => None,
202 };
203 if let Some(v) = result {
204 let s = if v.fract() == 0.0 && v.abs() < 1e15 {
205 format!("{}", v as i64)
206 } else {
207 format!("{v}")
208 };
209 return Expr::Value(ValueWithSpan {
210 value: Value::Number(s, false),
211 span: sqlparser::tokenizer::Span::empty(),
212 });
213 }
214 }
215
216 Expr::BinaryOp {
217 left: Box::new(left),
218 op,
219 right: Box::new(right),
220 }
221 }
222 Expr::Nested(inner) => Expr::Nested(Box::new(fold_expr(*inner))),
223 other => other,
224 }
225}
226
227fn expr_to_f64(expr: &sqlparser::ast::Expr) -> Option<f64> {
231 use sqlparser::ast::{Expr, Value, ValueWithSpan};
232 match expr {
233 Expr::Value(ValueWithSpan {
234 value: Value::Number(s, _),
235 ..
236 }) => s.parse::<f64>().ok(),
237 _ => None,
238 }
239}
240
241fn expr_to_str(expr: &sqlparser::ast::Expr) -> Option<&str> {
243 use sqlparser::ast::{Expr, Value, ValueWithSpan};
244 match expr {
245 Expr::Value(ValueWithSpan {
246 value: Value::SingleQuotedString(s),
247 ..
248 }) => Some(s.as_str()),
249 _ => None,
250 }
251}