blob: 80d6ee0a7b914e45af04d558672af743c45422b4 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Simplifier for Physical Expressions
use arrow::datatypes::Schema;
use datafusion_common::{
tree_node::{Transformed, TreeNode, TreeNodeRewriter},
Result,
};
use std::sync::Arc;
use crate::PhysicalExpr;
pub mod unwrap_cast;
/// Simplifies physical expressions by applying various optimizations
///
/// This can be useful after adapting expressions from a table schema
/// to a file schema. For example, casts added to match the types may
/// potentially be unwrapped.
pub struct PhysicalExprSimplifier<'a> {
schema: &'a Schema,
}
impl<'a> PhysicalExprSimplifier<'a> {
/// Create a new physical expression simplifier
pub fn new(schema: &'a Schema) -> Self {
Self { schema }
}
/// Simplify a physical expression
pub fn simplify(
&mut self,
expr: Arc<dyn PhysicalExpr>,
) -> Result<Arc<dyn PhysicalExpr>> {
Ok(expr.rewrite(self)?.data)
}
}
impl<'a> TreeNodeRewriter for PhysicalExprSimplifier<'a> {
type Node = Arc<dyn PhysicalExpr>;
fn f_up(&mut self, node: Self::Node) -> Result<Transformed<Self::Node>> {
// Apply unwrap cast optimization
#[cfg(test)]
let original_type = node.data_type(self.schema).unwrap();
let unwrapped = unwrap_cast::unwrap_cast_in_comparison(node, self.schema)?;
#[cfg(test)]
assert_eq!(
unwrapped.data.data_type(self.schema).unwrap(),
original_type,
"Simplified expression should have the same data type as the original"
);
Ok(unwrapped)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expressions::{col, lit, BinaryExpr, CastExpr, Literal, TryCastExpr};
use arrow::datatypes::{DataType, Field, Schema};
use datafusion_common::ScalarValue;
use datafusion_expr::Operator;
fn test_schema() -> Schema {
Schema::new(vec![
Field::new("c1", DataType::Int32, false),
Field::new("c2", DataType::Int64, false),
Field::new("c3", DataType::Utf8, false),
])
}
#[test]
fn test_simplify() {
let schema = test_schema();
let mut simplifier = PhysicalExprSimplifier::new(&schema);
// Create: cast(c2 as INT32) != INT32(99)
let column_expr = col("c2", &schema).unwrap();
let cast_expr = Arc::new(CastExpr::new(column_expr, DataType::Int32, None));
let literal_expr = lit(ScalarValue::Int32(Some(99)));
let binary_expr =
Arc::new(BinaryExpr::new(cast_expr, Operator::NotEq, literal_expr));
// Apply full simplification (uses TreeNodeRewriter)
let optimized = simplifier.simplify(binary_expr).unwrap();
let optimized_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
// Should be optimized to: c2 != INT64(99) (c2 is INT64, literal cast to match)
let left_expr = optimized_binary.left();
assert!(
left_expr.as_any().downcast_ref::<CastExpr>().is_none()
&& left_expr.as_any().downcast_ref::<TryCastExpr>().is_none()
);
let right_literal = optimized_binary
.right()
.as_any()
.downcast_ref::<Literal>()
.unwrap();
assert_eq!(right_literal.value(), &ScalarValue::Int64(Some(99)));
}
#[test]
fn test_nested_expression_simplification() {
let schema = test_schema();
let mut simplifier = PhysicalExprSimplifier::new(&schema);
// Create nested expression: (cast(c1 as INT64) > INT64(5)) OR (cast(c2 as INT32) <= INT32(10))
let c1_expr = col("c1", &schema).unwrap();
let c1_cast = Arc::new(CastExpr::new(c1_expr, DataType::Int64, None));
let c1_literal = lit(ScalarValue::Int64(Some(5)));
let c1_binary = Arc::new(BinaryExpr::new(c1_cast, Operator::Gt, c1_literal));
let c2_expr = col("c2", &schema).unwrap();
let c2_cast = Arc::new(CastExpr::new(c2_expr, DataType::Int32, None));
let c2_literal = lit(ScalarValue::Int32(Some(10)));
let c2_binary = Arc::new(BinaryExpr::new(c2_cast, Operator::LtEq, c2_literal));
let or_expr = Arc::new(BinaryExpr::new(c1_binary, Operator::Or, c2_binary));
// Apply simplification
let optimized = simplifier.simplify(or_expr).unwrap();
let or_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
// Verify left side: c1 > INT32(5)
let left_binary = or_binary
.left()
.as_any()
.downcast_ref::<BinaryExpr>()
.unwrap();
let left_left_expr = left_binary.left();
assert!(
left_left_expr.as_any().downcast_ref::<CastExpr>().is_none()
&& left_left_expr
.as_any()
.downcast_ref::<TryCastExpr>()
.is_none()
);
let left_literal = left_binary
.right()
.as_any()
.downcast_ref::<Literal>()
.unwrap();
assert_eq!(left_literal.value(), &ScalarValue::Int32(Some(5)));
// Verify right side: c2 <= INT64(10)
let right_binary = or_binary
.right()
.as_any()
.downcast_ref::<BinaryExpr>()
.unwrap();
let right_left_expr = right_binary.left();
assert!(
right_left_expr
.as_any()
.downcast_ref::<CastExpr>()
.is_none()
&& right_left_expr
.as_any()
.downcast_ref::<TryCastExpr>()
.is_none()
);
let right_literal = right_binary
.right()
.as_any()
.downcast_ref::<Literal>()
.unwrap();
assert_eq!(right_literal.value(), &ScalarValue::Int64(Some(10)));
}
}