|
15 | 15 | // specific language governing permissions and limitations |
16 | 16 | // under the License. |
17 | 17 |
|
18 | | -//! [`EliminateFilter`] replaces `where false` or `where null` with an empty relation. |
| 18 | +//! [`EliminateFilter`] removes filters that accept all or no input rows. |
19 | 19 |
|
20 | 20 | use datafusion_common::tree_node::Transformed; |
21 | 21 | use datafusion_common::{Result, ScalarValue}; |
22 | | -use datafusion_expr::{EmptyRelation, Expr, Filter, LogicalPlan}; |
| 22 | +use datafusion_expr::{EmptyRelation, Expr, Filter, LogicalPlan, Operator}; |
23 | 23 | use std::sync::Arc; |
24 | 24 |
|
25 | 25 | use crate::optimizer::ApplyOrder; |
26 | 26 | use crate::{OptimizerConfig, OptimizerRule}; |
27 | 27 |
|
28 | | -/// Optimization rule that eliminate the scalar value (true/false/null) filter |
29 | | -/// with an [LogicalPlan::EmptyRelation] |
| 28 | +/// Optimization rule that eliminates filters whose predicates always accept or |
| 29 | +/// reject their input rows. |
30 | 30 | /// |
31 | 31 | /// This saves time in planning and executing the query. |
32 | 32 | /// Note that this rule should be applied after simplify expressions optimizer rule. |
@@ -58,35 +58,132 @@ impl OptimizerRule for EliminateFilter { |
58 | 58 | plan: LogicalPlan, |
59 | 59 | _config: &dyn OptimizerConfig, |
60 | 60 | ) -> Result<Transformed<LogicalPlan>> { |
61 | | - match plan { |
62 | | - LogicalPlan::Filter(Filter { |
63 | | - predicate: Expr::Literal(ScalarValue::Boolean(v), _), |
64 | | - input, |
65 | | - .. |
66 | | - }) => match v { |
67 | | - Some(true) => Ok(Transformed::yes(Arc::unwrap_or_clone(input))), |
68 | | - Some(false) | None => Ok(Transformed::yes(LogicalPlan::EmptyRelation( |
69 | | - EmptyRelation { |
70 | | - produce_one_row: false, |
71 | | - schema: Arc::clone(input.schema()), |
72 | | - }, |
73 | | - ))), |
74 | | - }, |
75 | | - _ => Ok(Transformed::no(plan)), |
| 61 | + let LogicalPlan::Filter(Filter { |
| 62 | + predicate, input, .. |
| 63 | + }) = plan |
| 64 | + else { |
| 65 | + return Ok(Transformed::no(plan)); |
| 66 | + }; |
| 67 | + |
| 68 | + let simplified = simplify_filter_predicate(predicate); |
| 69 | + match simplified.predicate { |
| 70 | + FilterPredicate::AcceptsAll => { |
| 71 | + Ok(Transformed::yes(Arc::unwrap_or_clone(input))) |
| 72 | + } |
| 73 | + FilterPredicate::RejectsAll => Ok(Transformed::yes( |
| 74 | + LogicalPlan::EmptyRelation(EmptyRelation { |
| 75 | + produce_one_row: false, |
| 76 | + schema: Arc::clone(input.schema()), |
| 77 | + }), |
| 78 | + )), |
| 79 | + FilterPredicate::Expression(predicate) => Ok(Transformed::new_transformed( |
| 80 | + LogicalPlan::Filter(Filter::new(predicate, input)), |
| 81 | + simplified.transformed, |
| 82 | + )), |
76 | 83 | } |
77 | 84 | } |
78 | 85 | } |
79 | 86 |
|
| 87 | +/// The outcome of a predicate when only rows for which it evaluates to `TRUE` |
| 88 | +/// are retained. |
| 89 | +enum FilterPredicate { |
| 90 | + AcceptsAll, |
| 91 | + RejectsAll, |
| 92 | + Expression(Expr), |
| 93 | +} |
| 94 | + |
| 95 | +struct SimplifiedFilterPredicate { |
| 96 | + predicate: FilterPredicate, |
| 97 | + transformed: bool, |
| 98 | +} |
| 99 | + |
| 100 | +/// Simplifies positive `AND` / `OR` trees according to filter semantics. |
| 101 | +/// |
| 102 | +/// A `NULL` predicate rejects a row just like `FALSE`. This equivalence is safe |
| 103 | +/// throughout an `AND` / `OR` tree because both operators are monotonic with |
| 104 | +/// respect to whether their result can be `TRUE`. It is not safe through `NOT` |
| 105 | +/// or arbitrary expressions, so those are deliberately treated as leaves. |
| 106 | +fn simplify_filter_predicate(expr: Expr) -> SimplifiedFilterPredicate { |
| 107 | + match expr { |
| 108 | + Expr::Literal(ScalarValue::Boolean(Some(true)), _) => { |
| 109 | + simplified(FilterPredicate::AcceptsAll, false) |
| 110 | + } |
| 111 | + Expr::Literal(ScalarValue::Boolean(Some(false) | None), _) => { |
| 112 | + simplified(FilterPredicate::RejectsAll, false) |
| 113 | + } |
| 114 | + Expr::BinaryExpr(binary) if binary.op == Operator::And => { |
| 115 | + simplify_filter_and(*binary.left, *binary.right) |
| 116 | + } |
| 117 | + Expr::BinaryExpr(binary) if binary.op == Operator::Or => { |
| 118 | + simplify_filter_or(*binary.left, *binary.right) |
| 119 | + } |
| 120 | + expr => simplified(FilterPredicate::Expression(expr), false), |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +fn simplify_filter_and(left: Expr, right: Expr) -> SimplifiedFilterPredicate { |
| 125 | + let left = simplify_filter_predicate(left); |
| 126 | + let right = simplify_filter_predicate(right); |
| 127 | + let children_transformed = left.transformed || right.transformed; |
| 128 | + |
| 129 | + match (left.predicate, right.predicate) { |
| 130 | + (FilterPredicate::RejectsAll, _) | (_, FilterPredicate::RejectsAll) => { |
| 131 | + simplified(FilterPredicate::RejectsAll, true) |
| 132 | + } |
| 133 | + (FilterPredicate::AcceptsAll, predicate) |
| 134 | + | (predicate, FilterPredicate::AcceptsAll) => simplified(predicate, true), |
| 135 | + (FilterPredicate::Expression(left), FilterPredicate::Expression(right)) => { |
| 136 | + simplified( |
| 137 | + FilterPredicate::Expression(left.and(right)), |
| 138 | + children_transformed, |
| 139 | + ) |
| 140 | + } |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +fn simplify_filter_or(left: Expr, right: Expr) -> SimplifiedFilterPredicate { |
| 145 | + let left = simplify_filter_predicate(left); |
| 146 | + let right = simplify_filter_predicate(right); |
| 147 | + let children_transformed = left.transformed || right.transformed; |
| 148 | + |
| 149 | + match (left.predicate, right.predicate) { |
| 150 | + (FilterPredicate::AcceptsAll, _) | (_, FilterPredicate::AcceptsAll) => { |
| 151 | + simplified(FilterPredicate::AcceptsAll, true) |
| 152 | + } |
| 153 | + (FilterPredicate::RejectsAll, predicate) |
| 154 | + | (predicate, FilterPredicate::RejectsAll) => simplified(predicate, true), |
| 155 | + (FilterPredicate::Expression(left), FilterPredicate::Expression(right)) => { |
| 156 | + simplified( |
| 157 | + FilterPredicate::Expression(left.or(right)), |
| 158 | + children_transformed, |
| 159 | + ) |
| 160 | + } |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +fn simplified( |
| 165 | + predicate: FilterPredicate, |
| 166 | + transformed: bool, |
| 167 | +) -> SimplifiedFilterPredicate { |
| 168 | + SimplifiedFilterPredicate { |
| 169 | + predicate, |
| 170 | + transformed, |
| 171 | + } |
| 172 | +} |
| 173 | + |
80 | 174 | #[cfg(test)] |
81 | 175 | mod tests { |
| 176 | + use std::ops::Not; |
82 | 177 | use std::sync::Arc; |
83 | 178 |
|
84 | 179 | use crate::OptimizerContext; |
85 | 180 | use crate::assert_optimized_plan_eq_snapshot; |
86 | 181 | use datafusion_common::{Result, ScalarValue}; |
87 | 182 | use datafusion_expr::{Expr, col, lit, logical_plan::builder::LogicalPlanBuilder}; |
88 | 183 |
|
89 | | - use crate::eliminate_filter::EliminateFilter; |
| 184 | + use crate::eliminate_filter::{ |
| 185 | + EliminateFilter, FilterPredicate, simplify_filter_predicate, |
| 186 | + }; |
90 | 187 | use crate::test::*; |
91 | 188 | use datafusion_expr::test::function_stub::sum; |
92 | 189 |
|
@@ -134,6 +231,94 @@ mod tests { |
134 | 231 | assert_optimized_plan_equal!(plan, @"EmptyRelation: rows=0") |
135 | 232 | } |
136 | 233 |
|
| 234 | + #[test] |
| 235 | + fn filter_and_null() -> Result<()> { |
| 236 | + let null = Expr::Literal(ScalarValue::Boolean(None), None); |
| 237 | + |
| 238 | + let plan = LogicalPlanBuilder::from(test_table_scan()?) |
| 239 | + .filter(col("b").and(null.clone()))? |
| 240 | + .build()?; |
| 241 | + assert_optimized_plan_equal!(plan, @"EmptyRelation: rows=0")?; |
| 242 | + |
| 243 | + let plan = LogicalPlanBuilder::from(test_table_scan()?) |
| 244 | + .filter(null.and(col("b")))? |
| 245 | + .build()?; |
| 246 | + assert_optimized_plan_equal!(plan, @"EmptyRelation: rows=0")?; |
| 247 | + |
| 248 | + Ok(()) |
| 249 | + } |
| 250 | + |
| 251 | + #[test] |
| 252 | + fn filter_or_null() -> Result<()> { |
| 253 | + let null = Expr::Literal(ScalarValue::Boolean(None), None); |
| 254 | + |
| 255 | + let plan = LogicalPlanBuilder::from(test_table_scan()?) |
| 256 | + .filter(col("b").or(null.clone()))? |
| 257 | + .build()?; |
| 258 | + assert_optimized_plan_equal!( |
| 259 | + plan, |
| 260 | + @r" |
| 261 | + Filter: test.b |
| 262 | + TableScan: test |
| 263 | + " |
| 264 | + )?; |
| 265 | + |
| 266 | + let plan = LogicalPlanBuilder::from(test_table_scan()?) |
| 267 | + .filter(null.or(col("b")))? |
| 268 | + .build()?; |
| 269 | + assert_optimized_plan_equal!( |
| 270 | + plan, |
| 271 | + @r" |
| 272 | + Filter: test.b |
| 273 | + TableScan: test |
| 274 | + " |
| 275 | + )?; |
| 276 | + |
| 277 | + Ok(()) |
| 278 | + } |
| 279 | + |
| 280 | + #[test] |
| 281 | + fn filter_nested_null_predicates() -> Result<()> { |
| 282 | + let null = Expr::Literal(ScalarValue::Boolean(None), None); |
| 283 | + |
| 284 | + let plan = LogicalPlanBuilder::from(test_table_scan()?) |
| 285 | + .filter(col("b").and(col("b").or(null.clone())))? |
| 286 | + .build()?; |
| 287 | + assert_optimized_plan_equal!( |
| 288 | + plan, |
| 289 | + @r" |
| 290 | + Filter: test.b AND test.b |
| 291 | + TableScan: test |
| 292 | + " |
| 293 | + )?; |
| 294 | + |
| 295 | + let plan = LogicalPlanBuilder::from(test_table_scan()?) |
| 296 | + .filter(col("b").or(col("c").and(null)))? |
| 297 | + .build()?; |
| 298 | + assert_optimized_plan_equal!( |
| 299 | + plan, |
| 300 | + @r" |
| 301 | + Filter: test.b |
| 302 | + TableScan: test |
| 303 | + " |
| 304 | + )?; |
| 305 | + |
| 306 | + Ok(()) |
| 307 | + } |
| 308 | + |
| 309 | + #[test] |
| 310 | + fn filter_null_simplification_does_not_cross_not() { |
| 311 | + let null = Expr::Literal(ScalarValue::Boolean(None), None); |
| 312 | + let predicate = col("b").or(null).not(); |
| 313 | + let result = simplify_filter_predicate(predicate.clone()); |
| 314 | + |
| 315 | + assert!(!result.transformed); |
| 316 | + let FilterPredicate::Expression(actual) = result.predicate else { |
| 317 | + panic!("expected the predicate to remain an expression") |
| 318 | + }; |
| 319 | + assert_eq!(actual, predicate); |
| 320 | + } |
| 321 | + |
137 | 322 | #[test] |
138 | 323 | fn filter_false_nested() -> Result<()> { |
139 | 324 | let filter_expr = lit(false); |
|
0 commit comments