## Coverage Report

#### Created: 2025-11-22 06:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /Users/andrewlamb/Software/datafusion/datafusion/physical-expr/src/simplifier/mod.rs

    Line
    Count
    Source
    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
    //! Simplifier for Physical Expressions
    19
    20
    use arrow::datatypes::Schema;
    21
    use datafusion_common::{
    22
        tree_node::{Transformed, TreeNode, TreeNodeRewriter},
    23
        Result,
    24
    };
    25
    use std::sync::Arc;
    26
    27
    use crate::PhysicalExpr;
    28
    29
    pub mod unwrap_cast;
    30
    31
    /// Simplifies physical expressions by applying various optimizations
    32
    ///
    33
    /// This can be useful after adapting expressions from a table schema
    34
    /// to a file schema. For example, casts added to match the types may
    35
    /// potentially be unwrapped.
    36
    pub struct PhysicalExprSimplifier<'a> {
    37
        schema: &'a Schema,
    38
    }
    39
    40
    impl<'a> PhysicalExprSimplifier<'a> {
    41
        /// Create a new physical expression simplifier
    42
    0
        pub fn new(schema: &'a Schema) -> Self {
    43
    0
            Self { schema }
    44
    0
        }
    45
    46
        /// Simplify a physical expression
    47
    0
        pub fn simplify(
    48
    0
            &mut self,
    49
    0
            expr: Arc<dyn PhysicalExpr>,
    50
    0
        ) -> Result<Arc<dyn PhysicalExpr>> {
    51
    0
            Ok(expr.rewrite(self)?.data)
    52
    0
        }
    53
    }
    54
    55
    impl<'a> TreeNodeRewriter for PhysicalExprSimplifier<'a> {
    56
        type Node = Arc<dyn PhysicalExpr>;
    57
    58
    0
        fn f_up(&mut self, node: Self::Node) -> Result<Transformed<Self::Node>> {
    59
            // Apply unwrap cast optimization
    60
            #[cfg(test)]
    61
    0
            let original_type = node.data_type(self.schema).unwrap();
    62
    0
            let unwrapped = unwrap_cast::unwrap_cast_in_comparison(node, self.schema)?;
    63
            #[cfg(test)]
    64
    0
            assert_eq!(
    65
    0
                unwrapped.data.data_type(self.schema).unwrap(),
    66
                original_type,
    67
    0
                "Simplified expression should have the same data type as the original"
    68
            );
    69
    0
            Ok(unwrapped)
    70
    0
        }
    71
    }
    72
    73
    #[cfg(test)]
    74
    mod tests {
    75
        use super::*;
    76
        use crate::expressions::{col, lit, BinaryExpr, CastExpr, Literal, TryCastExpr};
    77
        use arrow::datatypes::{DataType, Field, Schema};
    78
        use datafusion_common::ScalarValue;
    79
        use datafusion_expr::Operator;
    80
    81
    0
        fn test_schema() -> Schema {
    82
    0
            Schema::new(vec![
    83
    0
                Field::new("c1", DataType::Int32, false),
    84
    0
                Field::new("c2", DataType::Int64, false),
    85
    0
                Field::new("c3", DataType::Utf8, false),
    86
            ])
    87
    0
        }
    88
    89
        #[test]
    90
    0
        fn test_simplify() {
    91
    0
            let schema = test_schema();
    92
    0
            let mut simplifier = PhysicalExprSimplifier::new(&schema);
    93
    94
            // Create: cast(c2 as INT32) != INT32(99)
    95
    0
            let column_expr = col("c2", &schema).unwrap();
    96
    0
            let cast_expr = Arc::new(CastExpr::new(column_expr, DataType::Int32, None));
    97
    0
            let literal_expr = lit(ScalarValue::Int32(Some(99)));
    98
    0
            let binary_expr =
    99
    0
                Arc::new(BinaryExpr::new(cast_expr, Operator::NotEq, literal_expr));
    100
    101
            // Apply full simplification (uses TreeNodeRewriter)
    102
    0
            let optimized = simplifier.simplify(binary_expr).unwrap();
    103
    104
    0
            let optimized_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
    105
    106
            // Should be optimized to: c2 != INT64(99) (c2 is INT64, literal cast to match)
    107
    0
            let left_expr = optimized_binary.left();
    108
    0
            assert!(
    109
    0
                left_expr.as_any().downcast_ref::<CastExpr>().is_none()
    110
    0
                    && left_expr.as_any().downcast_ref::<TryCastExpr>().is_none()
    111
            );
    112
    0
            let right_literal = optimized_binary
    113
    0
                .right()
    114
    0
                .as_any()
    115
    0
                .downcast_ref::<Literal>()
    116
    0
                .unwrap();
    117
    0
            assert_eq!(right_literal.value(), &ScalarValue::Int64(Some(99)));
    118
    0
        }
    119
    120
        #[test]
    121
    0
        fn test_nested_expression_simplification() {
    122
    0
            let schema = test_schema();
    123
    0
            let mut simplifier = PhysicalExprSimplifier::new(&schema);
    124
    125
            // Create nested expression: (cast(c1 as INT64) > INT64(5)) OR (cast(c2 as INT32) <= INT32(10))
    126
    0
            let c1_expr = col("c1", &schema).unwrap();
    127
    0
            let c1_cast = Arc::new(CastExpr::new(c1_expr, DataType::Int64, None));
    128
    0
            let c1_literal = lit(ScalarValue::Int64(Some(5)));
    129
    0
            let c1_binary = Arc::new(BinaryExpr::new(c1_cast, Operator::Gt, c1_literal));
    130
    131
    0
            let c2_expr = col("c2", &schema).unwrap();
    132
    0
            let c2_cast = Arc::new(CastExpr::new(c2_expr, DataType::Int32, None));
    133
    0
            let c2_literal = lit(ScalarValue::Int32(Some(10)));
    134
    0
            let c2_binary = Arc::new(BinaryExpr::new(c2_cast, Operator::LtEq, c2_literal));
    135
    136
    0
            let or_expr = Arc::new(BinaryExpr::new(c1_binary, Operator::Or, c2_binary));
    137
    138
            // Apply simplification
    139
    0
            let optimized = simplifier.simplify(or_expr).unwrap();
    140
    141
    0
            let or_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
    142
    143
            // Verify left side: c1 > INT32(5)
    144
    0
            let left_binary = or_binary
    145
    0
                .left()
    146
    0
                .as_any()
    147
    0
                .downcast_ref::<BinaryExpr>()
    148
    0
                .unwrap();
    149
    0
            let left_left_expr = left_binary.left();
    150
    0
            assert!(
    151
    0
                left_left_expr.as_any().downcast_ref::<CastExpr>().is_none()
    152
    0
                    && left_left_expr
    153
    0
                        .as_any()
    154
    0
                        .downcast_ref::<TryCastExpr>()
    155
    0
                        .is_none()
    156
            );
    157
    0
            let left_literal = left_binary
    158
    0
                .right()
    159
    0
                .as_any()
    160
    0
                .downcast_ref::<Literal>()
    161
    0
                .unwrap();
    162
    0
            assert_eq!(left_literal.value(), &ScalarValue::Int32(Some(5)));
    163
    164
            // Verify right side: c2 <= INT64(10)
    165
    0
            let right_binary = or_binary
    166
    0
                .right()
    167
    0
                .as_any()
    168
    0
                .downcast_ref::<BinaryExpr>()
    169
    0
                .unwrap();
    170
    0
            let right_left_expr = right_binary.left();
    171
    0
            assert!(
    172
    0
                right_left_expr
    173
    0
                    .as_any()
    174
    0
                    .downcast_ref::<CastExpr>()
    175
    0
                    .is_none()
    176
    0
                    && right_left_expr
    177
    0
                        .as_any()
    178
    0
                        .downcast_ref::<TryCastExpr>()
    179
    0
                        .is_none()
    180
            );
    181
    0
            let right_literal = right_binary
    182
    0
                .right()
    183
    0
                .as_any()
    184
    0
                .downcast_ref::<Literal>()
    185
    0
                .unwrap();
    186
    0
            assert_eq!(right_literal.value(), &ScalarValue::Int64(Some(10)));
    187
    0
        }
    188
    }