Skip to main content

dbx_core/sql/optimizer/
predicate_pushdown.rs

1//! Rule 1: Predicate Pushdown
2//!
3//! Filter를 Scan에 가까이 이동하여 I/O 감소
4
5use crate::error::DbxResult;
6use crate::sql::planner::{BinaryOperator, Expr, LogicalPlan};
7
8use super::OptimizationRule;
9
10/// Filter를 Scan에 가까이 이동하여 I/O 감소
11pub struct PredicatePushdownRule;
12
13impl OptimizationRule for PredicatePushdownRule {
14    fn name(&self) -> &str {
15        "PredicatePushdown"
16    }
17
18    fn apply(&self, plan: LogicalPlan) -> DbxResult<LogicalPlan> {
19        self.push_down(plan)
20    }
21}
22
23impl PredicatePushdownRule {
24    fn push_down(&self, plan: LogicalPlan) -> DbxResult<LogicalPlan> {
25        match plan {
26            LogicalPlan::Filter { input, predicate } => {
27                let optimized_input = self.push_down(*input)?;
28                match optimized_input {
29                    LogicalPlan::Project {
30                        input: project_input,
31                        projections: columns,
32                    } if self.can_push_through_project(&predicate, &columns) => {
33                        let pushed = self.push_down(LogicalPlan::Filter {
34                            input: project_input,
35                            predicate,
36                        })?;
37                        Ok(LogicalPlan::Project {
38                            input: Box::new(pushed),
39                            projections: columns,
40                        })
41                    }
42                    LogicalPlan::Scan {
43                        table,
44                        columns,
45                        filter: None,
46                    } => Ok(LogicalPlan::Scan {
47                        table,
48                        columns,
49                        filter: Some(predicate),
50                    }),
51                    LogicalPlan::Scan {
52                        table,
53                        columns,
54                        filter: Some(existing),
55                    } => Ok(LogicalPlan::Scan {
56                        table,
57                        columns,
58                        filter: Some(Expr::BinaryOp {
59                            left: Box::new(existing),
60                            op: BinaryOperator::And,
61                            right: Box::new(predicate),
62                        }),
63                    }),
64                    other => Ok(LogicalPlan::Filter {
65                        input: Box::new(other),
66                        predicate,
67                    }),
68                }
69            }
70            LogicalPlan::Project { input, projections } => Ok(LogicalPlan::Project {
71                input: Box::new(self.push_down(*input)?),
72                projections,
73            }),
74            LogicalPlan::Sort { input, order_by } => Ok(LogicalPlan::Sort {
75                input: Box::new(self.push_down(*input)?),
76                order_by,
77            }),
78            LogicalPlan::Limit {
79                input,
80                count,
81                offset,
82            } => Ok(LogicalPlan::Limit {
83                input: Box::new(self.push_down(*input)?),
84                count,
85                offset,
86            }),
87            LogicalPlan::Aggregate {
88                input,
89                group_by,
90                aggregates,
91            } => Ok(LogicalPlan::Aggregate {
92                input: Box::new(self.push_down(*input)?),
93                group_by,
94                aggregates,
95            }),
96            other => Ok(other),
97        }
98    }
99
100    /// Check if a predicate references only columns available before projection.
101    fn can_push_through_project(
102        &self,
103        predicate: &Expr,
104        _projections: &[(Expr, Option<String>)],
105    ) -> bool {
106        self.is_simple_column_predicate(predicate)
107    }
108
109    fn is_simple_column_predicate(&self, expr: &Expr) -> bool {
110        match expr {
111            Expr::Column(_) | Expr::Literal(_) => true,
112            Expr::BinaryOp { left, right, .. } => {
113                self.is_simple_column_predicate(left) && self.is_simple_column_predicate(right)
114            }
115            Expr::IsNull(inner) | Expr::IsNotNull(inner) => self.is_simple_column_predicate(inner),
116            _ => false,
117        }
118    }
119}