1use crate::optimizer::OptimizerRule;
31use crate::{LogicalPlan, NodeOp, PlanNode};
32
33pub struct ConstantFoldingRule;
36
37impl OptimizerRule for ConstantFoldingRule {
38 fn name(&self) -> &str {
39 "constant-folding"
40 }
41
42 fn apply(&self, plan: &LogicalPlan) -> Option<LogicalPlan> {
43 let mut changed = false;
44 let mut new_nodes: Vec<PlanNode> = Vec::with_capacity(plan.nodes().len());
45 for node in plan.nodes() {
46 let new_op = match node.op() {
47 Some(NodeOp::Filter { predicate }) => {
48 let folded = fold_predicate(predicate);
49 if folded.as_deref() != Some(predicate.as_str()) {
50 changed = true;
51 }
52 NodeOp::Filter {
53 predicate: folded.unwrap_or_else(|| predicate.clone()),
54 }
55 }
56 Some(other) => other.clone(),
57 None => return None,
58 };
59 let mut new_node = PlanNode::new(node.id(), node.label(), node.kind())
60 .with_op(new_op)
61 .with_inputs(node.inputs().to_vec());
62 let partitioning = node.partitioning().clone();
63 new_node = new_node.with_partitioning(partitioning);
64 if let Some(estimated_rows) = node.estimated_rows() {
65 new_node = new_node.with_estimated_rows(Some(estimated_rows));
66 }
67 if node.broadcast_eligible() {
68 new_node = new_node.with_broadcast_eligible(true);
69 }
70 let schema = node.output_schema();
76 if !schema.is_empty() {
77 new_node = new_node.with_output_schema(schema.clone());
78 }
79 new_nodes.push(new_node);
80 }
81 if !changed {
82 return None;
83 }
84 let mut new_plan = LogicalPlan::new(plan.name(), plan.kind());
86 for node in &new_nodes {
87 new_plan.add_node(node.clone());
88 }
89 if let Some(parts) = plan.shuffle_partitions() {
90 new_plan = new_plan.with_shuffle_partitions(Some(parts));
91 }
92 Some(new_plan)
93 }
94}
95
96fn fold_predicate(predicate: &str) -> Option<String> {
102 let trimmed = predicate.trim();
103 if trimmed.is_empty() {
104 return Some("TRUE".to_string());
105 }
106 let folded = try_fold(trimmed);
107 match folded {
108 FoldResult::True => Some("TRUE".to_string()),
109 FoldResult::False => Some("FALSE".to_string()),
110 FoldResult::Expr(s) if s == trimmed => None,
111 FoldResult::Expr(s) => Some(s),
112 FoldResult::Unknown => None,
113 }
114}
115
116enum FoldResult {
117 True,
119 False,
121 Expr(String),
123 Unknown,
125}
126
127fn try_fold(input: &str) -> FoldResult {
133 let mut p = Parser::new(input);
134 let result = p.parse_or();
135 if !p.eof() {
136 return FoldResult::Expr(input.to_string());
137 }
138 match result {
139 Ok(FoldValue::Bool(b)) => {
140 if b {
141 FoldResult::True
142 } else {
143 FoldResult::False
144 }
145 }
146 Ok(FoldValue::Int(n)) => FoldResult::Expr(n.to_string()),
147 Ok(FoldValue::Str(s)) => FoldResult::Expr(s),
148 Ok(FoldValue::Column(_)) | Err(()) => FoldResult::Unknown,
149 }
150}
151
152#[derive(Debug, Clone, PartialEq)]
153enum FoldValue {
154 Bool(bool),
155 Int(i64),
156 Str(String),
158 Column(String),
162}
163
164struct Parser<'a> {
165 bytes: &'a [u8],
166 pos: usize,
167}
168
169impl<'a> Parser<'a> {
170 fn new(s: &'a str) -> Self {
171 Self {
172 bytes: s.as_bytes(),
173 pos: 0,
174 }
175 }
176
177 fn eof(&self) -> bool {
178 self.pos >= self.bytes.len()
179 }
180
181 fn skip_ws(&mut self) {
182 while self.pos < self.bytes.len()
183 && self
184 .bytes
185 .get(self.pos)
186 .is_some_and(|b| b.is_ascii_whitespace())
187 {
188 self.pos += 1;
189 }
190 }
191
192 fn peek(&self) -> Option<u8> {
193 self.bytes.get(self.pos).copied()
194 }
195
196 fn bump(&mut self) -> Option<u8> {
197 let b = self.bytes.get(self.pos).copied();
198 if b.is_some() {
199 self.pos += 1;
200 }
201 b
202 }
203
204 fn eat(&mut self, c: u8) -> bool {
205 self.skip_ws();
206 if self.peek() == Some(c) {
207 self.pos += 1;
208 true
209 } else {
210 false
211 }
212 }
213
214 fn eat_kw(&mut self, kw: &[u8]) -> bool {
215 self.skip_ws();
216 if self
217 .bytes
218 .get(self.pos..)
219 .is_some_and(|s| s.starts_with(kw))
220 {
221 let after = self.pos + kw.len();
223 if after == self.bytes.len()
224 || self
225 .bytes
226 .get(after)
227 .is_none_or(|b| b.is_ascii_whitespace())
228 {
229 self.pos = after;
230 return true;
231 }
232 }
233 false
234 }
235
236 fn parse_or(&mut self) -> Result<FoldValue, ()> {
237 let mut left = self.parse_and()?;
238 loop {
239 self.skip_ws();
240 if self.eat_kw(b"OR") {
241 let right = self.parse_and()?;
242 left = match (left.clone(), right) {
248 (FoldValue::Bool(true), _) | (_, FoldValue::Bool(true)) => {
249 FoldValue::Bool(true)
250 }
251 (FoldValue::Bool(false), r) => r,
252 (l, FoldValue::Bool(false)) => l,
253 (FoldValue::Column(_), _) | (_, FoldValue::Column(_)) => {
256 return Err(());
257 }
258 _ => return Err(()),
259 };
260 } else {
261 break;
262 }
263 }
264 Ok(left)
265 }
266
267 fn parse_and(&mut self) -> Result<FoldValue, ()> {
268 let mut left = self.parse_not()?;
269 loop {
270 self.skip_ws();
271 if self.eat_kw(b"AND") {
272 let right = self.parse_not()?;
273 left = match (left.clone(), right) {
279 (FoldValue::Bool(false), _) | (_, FoldValue::Bool(false)) => {
280 FoldValue::Bool(false)
281 }
282 (FoldValue::Bool(true), r) => r,
283 (l, FoldValue::Bool(true)) => l,
284 (FoldValue::Column(_), _) | (_, FoldValue::Column(_)) => {
285 return Err(());
286 }
287 _ => return Err(()),
288 };
289 } else {
290 break;
291 }
292 }
293 Ok(left)
294 }
295
296 fn parse_not(&mut self) -> Result<FoldValue, ()> {
297 self.skip_ws();
298 if self.eat_kw(b"NOT") {
299 let v = self.parse_cmp()?;
300 return Ok(match v {
301 FoldValue::Bool(b) => FoldValue::Bool(!b),
302 _ => return Err(()),
303 });
304 }
305 self.parse_cmp()
306 }
307
308 fn parse_cmp(&mut self) -> Result<FoldValue, ()> {
309 let start = self.pos;
310 let left = self.parse_add()?;
311 self.skip_ws();
312 let op = if self.eat(b'<') {
314 if self.eat(b'=') { "<=" } else { "<" }
315 } else if self.eat(b'>') {
316 if self.eat(b'=') { ">=" } else { ">" }
317 } else if self.eat(b'=') {
318 if self.eat(b'=') { "==" } else { "=" }
319 } else if self.eat(b'!') {
320 if self.eat(b'=') {
321 "!="
322 } else {
323 return Err(());
324 }
325 } else {
326 return Ok(left);
327 };
328 let right = self.parse_add()?;
329 if matches!(left, FoldValue::Column(_)) || matches!(right, FoldValue::Column(_)) {
335 let end = self.pos;
336 let text = std::str::from_utf8(self.bytes.get(start..end).ok_or(())?)
337 .map_err(|_| ())?
338 .trim()
339 .to_string();
340 return Ok(FoldValue::Column(text));
341 }
342 self.cmp_op(op, left, right)
343 }
344
345 fn cmp_op(&self, op: &str, left: FoldValue, right: FoldValue) -> Result<FoldValue, ()> {
346 match (left, right) {
347 (FoldValue::Int(a), FoldValue::Int(b)) => {
348 let r = match op {
349 "=" | "==" => a == b,
350 "!=" => a != b,
351 "<" => a < b,
352 "<=" => a <= b,
353 ">" => a > b,
354 ">=" => a >= b,
355 _ => return Err(()),
356 };
357 Ok(FoldValue::Bool(r))
358 }
359 (FoldValue::Str(a), FoldValue::Str(b)) => match op {
360 "=" | "==" => Ok(FoldValue::Bool(a == b)),
361 "!=" => Ok(FoldValue::Bool(a != b)),
362 _ => Err(()),
363 },
364 _ => Err(()),
365 }
366 }
367
368 fn parse_add(&mut self) -> Result<FoldValue, ()> {
369 let mut left = self.parse_mul()?;
370 loop {
371 self.skip_ws();
372 let op = if self.eat(b'+') {
373 Some(b'+')
374 } else if self.eat(b'-') {
375 Some(b'-')
376 } else {
377 None
378 };
379 let Some(op) = op else { break };
380 let right = self.parse_mul()?;
381 left = match (left, right) {
382 (FoldValue::Int(a), FoldValue::Int(b)) => match op {
383 b'+' => FoldValue::Int(a.checked_add(b).ok_or(())?),
384 b'-' => FoldValue::Int(a.checked_sub(b).ok_or(())?),
385 _ => return Err(()),
386 },
387 _ => return Err(()),
388 };
389 }
390 Ok(left)
391 }
392
393 fn parse_mul(&mut self) -> Result<FoldValue, ()> {
394 let mut left = self.parse_unary()?;
395 loop {
396 self.skip_ws();
397 let op = if self.eat(b'*') {
398 Some(b'*')
399 } else if self.eat(b'/') {
400 Some(b'/')
401 } else {
402 None
403 };
404 let Some(op) = op else { break };
405 let right = self.parse_unary()?;
406 left = match (left, right) {
407 (FoldValue::Int(a), FoldValue::Int(b)) => match op {
408 b'*' => FoldValue::Int(a.checked_mul(b).ok_or(())?),
409 b'/' => {
410 if b == 0 {
411 return Err(());
412 }
413 FoldValue::Int(a.checked_div(b).ok_or(())?)
414 }
415 _ => return Err(()),
416 },
417 _ => return Err(()),
418 };
419 }
420 Ok(left)
421 }
422
423 fn parse_unary(&mut self) -> Result<FoldValue, ()> {
424 self.skip_ws();
425 if self.eat(b'-') {
426 let v = self.parse_atom()?;
427 return match v {
428 FoldValue::Int(n) => n.checked_neg().map(FoldValue::Int).ok_or(()),
429 _ => Err(()),
430 };
431 }
432 if self.eat(b'+') {
433 return self.parse_atom();
434 }
435 self.parse_atom()
436 }
437
438 fn parse_atom(&mut self) -> Result<FoldValue, ()> {
439 self.skip_ws();
440 if self.eat(b'(') {
441 let v = self.parse_or()?;
442 self.skip_ws();
443 if !self.eat(b')') {
444 return Err(());
445 }
446 return Ok(v);
447 }
448 if self.eat_kw(b"TRUE") {
450 return Ok(FoldValue::Bool(true));
451 }
452 if self.eat_kw(b"FALSE") {
453 return Ok(FoldValue::Bool(false));
454 }
455 if self.peek() == Some(b'\'') {
457 self.pos += 1;
458 let start = self.pos;
459 while let Some(c) = self.peek() {
460 if c == b'\\' {
461 self.pos += 1;
462 if self.bump().is_none() {
463 return Err(());
464 }
465 continue;
466 }
467 if c == b'\'' {
468 break;
469 }
470 self.pos += 1;
471 }
472 if !self.eat(b'\'') {
473 return Err(());
474 }
475 let s = std::str::from_utf8(
476 self.bytes
477 .get(start..self.pos.saturating_sub(1))
478 .ok_or(())?,
479 )
480 .map_err(|_| ())?
481 .to_string();
482 return Ok(FoldValue::Str(s));
483 }
484 let start = self.pos;
486 while let Some(c) = self.peek() {
487 if c.is_ascii_digit() {
488 self.pos += 1;
489 } else {
490 break;
491 }
492 }
493 if start < self.pos {
494 let s =
495 std::str::from_utf8(self.bytes.get(start..self.pos).ok_or(())?).map_err(|_| ())?;
496 return s.parse::<i64>().map(FoldValue::Int).map_err(|_| ());
497 }
498 let id_start = self.pos;
506 while let Some(c) = self.peek() {
507 if c.is_ascii_alphanumeric() || c == b'_' {
508 self.pos += 1;
509 } else {
510 break;
511 }
512 }
513 if id_start < self.pos {
514 if self.peek() == Some(b'(') {
518 self.pos = id_start;
520 return Err(());
521 }
522 let name = std::str::from_utf8(self.bytes.get(id_start..self.pos).ok_or(())?)
523 .map_err(|_| ())?
524 .to_string();
525 return Ok(FoldValue::Column(name));
526 }
527 Err(())
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534
535 fn assert_fold(input: &str, expected: &str) {
536 let result = fold_predicate(input);
537 assert_eq!(
538 result.as_deref(),
539 Some(expected),
540 "fold_predicate({input:?}) expected {expected:?}, got {result:?}"
541 );
542 }
543
544 #[test]
545 fn folds_arithmetic() {
546 assert_fold("1 + 1", "2");
547 assert_fold("(2 * 3) + 4", "10");
548 assert_fold("10 - 3", "7");
549 assert_fold("12 / 4", "3");
550 assert_fold("-(5)", "-5");
551 }
552
553 #[test]
554 fn folds_comparisons() {
555 assert_fold("1 = 1", "TRUE");
556 assert_fold("1 = 0", "FALSE");
557 assert_fold("2 > 1", "TRUE");
558 assert_fold("2 < 1", "FALSE");
559 }
560
561 #[test]
562 fn folds_logical_connectives_when_other_side_is_constant() {
563 assert_eq!(fold_predicate("TRUE AND col = 1"), None);
567 assert_fold("FALSE AND col = 1", "FALSE");
569 assert_fold("TRUE OR col = 1", "TRUE");
571 assert_eq!(fold_predicate("FALSE OR col = 1"), None);
574 assert_fold("NOT TRUE", "FALSE");
575 assert_fold("NOT FALSE", "TRUE");
576 }
577
578 #[test]
579 fn leaves_already_folded_expressions_alone() {
580 let r = fold_predicate("1 + 1");
581 assert_eq!(r.as_deref(), Some("2"));
582 }
583
584 #[test]
585 fn leaves_column_references_alone() {
586 assert_eq!(fold_predicate("col = 1"), None);
587 assert_eq!(fold_predicate("a + b"), None);
588 }
589
590 #[test]
591 fn folds_nested_boolean_when_reduces_to_constant() {
592 assert_fold("1 = 0 AND col = 1", "FALSE");
595 assert_eq!(fold_predicate("1 = 1 AND col = 1"), None);
600 }
601}