datafusion_physical_expr/simplifier/not.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Simplify NOT expressions in physical expressions
19//!
20//! This module provides optimizations for NOT expressions such as:
21//! - Double negation elimination: NOT(NOT(expr)) -> expr
22//! - NOT with binary comparisons: NOT(a = b) -> a != b
23//! - NOT with IN expressions: NOT(a IN (list)) -> a NOT IN (list)
24//! - De Morgan's laws: NOT(A AND B) -> NOT A OR NOT B
25//! - Constant folding: NOT(TRUE) -> FALSE, NOT(FALSE) -> TRUE
26//!
27//! This function is designed to work with TreeNodeRewriter's f_up traversal,
28//! which means children are already simplified when this function is called.
29//! The TreeNodeRewriter will automatically call this function repeatedly until
30//! no more transformations are possible.
31
32use std::sync::Arc;
33
34use arrow::datatypes::Schema;
35use datafusion_common::{Result, ScalarValue, tree_node::Transformed};
36use datafusion_expr::Operator;
37
38use crate::PhysicalExpr;
39use crate::expressions::{BinaryExpr, InListExpr, Literal, NotExpr, in_list, lit};
40
41/// Attempts to simplify NOT expressions by applying one level of transformation
42///
43/// This function applies a single simplification rule and returns. When used with
44/// TreeNodeRewriter, multiple passes will automatically be applied until no more
45/// transformations are possible.
46pub fn simplify_not_expr(
47 expr: &Arc<dyn PhysicalExpr>,
48 schema: &Schema,
49) -> Result<Transformed<Arc<dyn PhysicalExpr>>> {
50 // Check if this is a NOT expression
51 let not_expr = match expr.as_any().downcast_ref::<NotExpr>() {
52 Some(not_expr) => not_expr,
53 None => return Ok(Transformed::no(Arc::clone(expr))),
54 };
55
56 let inner_expr = not_expr.arg();
57
58 // Handle NOT(NOT(expr)) -> expr (double negation elimination)
59 if let Some(inner_not) = inner_expr.as_any().downcast_ref::<NotExpr>() {
60 return Ok(Transformed::yes(Arc::clone(inner_not.arg())));
61 }
62
63 // Handle NOT(literal) -> !literal
64 if let Some(literal) = inner_expr.as_any().downcast_ref::<Literal>() {
65 if let ScalarValue::Boolean(Some(val)) = literal.value() {
66 return Ok(Transformed::yes(lit(ScalarValue::Boolean(Some(!val)))));
67 }
68 if let ScalarValue::Boolean(None) = literal.value() {
69 return Ok(Transformed::yes(lit(ScalarValue::Boolean(None))));
70 }
71 }
72
73 // Handle NOT(IN list) -> NOT IN list
74 if let Some(in_list_expr) = inner_expr.as_any().downcast_ref::<InListExpr>() {
75 let negated = !in_list_expr.negated();
76 let new_in_list = in_list(
77 Arc::clone(in_list_expr.expr()),
78 in_list_expr.list().to_vec(),
79 &negated,
80 schema,
81 )?;
82 return Ok(Transformed::yes(new_in_list));
83 }
84
85 // Handle NOT(binary_expr)
86 if let Some(binary_expr) = inner_expr.as_any().downcast_ref::<BinaryExpr>() {
87 if let Some(negated_op) = binary_expr.op().negate() {
88 let new_binary = Arc::new(BinaryExpr::new(
89 Arc::clone(binary_expr.left()),
90 negated_op,
91 Arc::clone(binary_expr.right()),
92 ));
93 return Ok(Transformed::yes(new_binary));
94 }
95
96 // Handle De Morgan's laws for AND/OR
97 match binary_expr.op() {
98 Operator::And => {
99 // NOT(A AND B) -> NOT A OR NOT B
100 let not_left: Arc<dyn PhysicalExpr> =
101 Arc::new(NotExpr::new(Arc::clone(binary_expr.left())));
102 let not_right: Arc<dyn PhysicalExpr> =
103 Arc::new(NotExpr::new(Arc::clone(binary_expr.right())));
104 let new_binary =
105 Arc::new(BinaryExpr::new(not_left, Operator::Or, not_right));
106 return Ok(Transformed::yes(new_binary));
107 }
108 Operator::Or => {
109 // NOT(A OR B) -> NOT A AND NOT B
110 let not_left: Arc<dyn PhysicalExpr> =
111 Arc::new(NotExpr::new(Arc::clone(binary_expr.left())));
112 let not_right: Arc<dyn PhysicalExpr> =
113 Arc::new(NotExpr::new(Arc::clone(binary_expr.right())));
114 let new_binary =
115 Arc::new(BinaryExpr::new(not_left, Operator::And, not_right));
116 return Ok(Transformed::yes(new_binary));
117 }
118 _ => {}
119 }
120 }
121
122 // If no simplification possible, return the original expression
123 Ok(Transformed::no(Arc::clone(expr)))
124}