## 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/expr/src/predicate_bounds.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
    use crate::{Between, BinaryExpr, Expr, ExprSchemable};
    19
    use arrow::datatypes::DataType;
    20
    use datafusion_common::tree_node::{TreeNode, TreeNodeRecursion};
    21
    use datafusion_common::{ExprSchema, Result, ScalarValue};
    22
    use datafusion_expr_common::interval_arithmetic::NullableInterval;
    23
    use datafusion_expr_common::operator::Operator;
    24
    25
    /// Computes the output interval for the given boolean expression based on statically
    26
    /// available information.
    27
    ///
    28
    /// # Arguments
    29
    ///
    30
    /// * `predicate` - The boolean expression to analyze
    31
    /// * `is_null` - A callback function that provides additional nullability information for
    32
    ///   expressions. When called with an expression, it should return:
    33
    ///   - `Some(true)` if the expression is known to evaluate to NULL
    34
    ///   - `Some(false)` if the expression is known to NOT evaluate to NULL
    35
    ///   - `None` if the nullability cannot be determined
    36
    ///
    37
    ///   This callback allows the caller to provide context-specific knowledge about expression
    38
    ///   nullability that cannot be determined from the schema alone. For example, it can be used
    39
    ///   to indicate that a particular column reference is known to be NULL in a specific context,
    40
    ///   or that certain expressions will never be NULL based on runtime constraints.
    41
    ///
    42
    /// * `input_schema` - Schema information for resolving expression types and nullability
    43
    ///
    44
    /// # Return Value
    45
    ///
    46
    /// The function returns a [NullableInterval] that describes the possible boolean values the
    47
    /// predicate can evaluate to.
    48
    ///
    49
    0
    pub(super) fn evaluate_bounds(
    50
    0
        predicate: &Expr,
    51
    0
        input_schema: &dyn ExprSchema,
    52
    0
    ) -> Result<NullableInterval> {
    53
    0
        let evaluator = PredicateBoundsEvaluator { input_schema };
    54
    0
        evaluator.evaluate_bounds(predicate)
    55
    0
    }
    56
    57
    struct PredicateBoundsEvaluator<'a> {
    58
        input_schema: &'a dyn ExprSchema,
    59
    }
    60
    61
    impl PredicateBoundsEvaluator<'_> {
    62
        /// Derives the bounds of the given boolean expression
    63
    0
        fn evaluate_bounds(&self, predicate: &Expr) -> Result<NullableInterval> {
    64
    0
            Ok(match predicate {
    65
    0
                Expr::Literal(scalar, _) => {
    66
                    // Interpret literals as boolean, coercing if necessary
    67
    0
                    match scalar {
    68
    0
                        ScalarValue::Null => NullableInterval::UNKNOWN,
    69
    0
                        ScalarValue::Boolean(b) => match b {
    70
    0
                            Some(true) => NullableInterval::TRUE,
    71
    0
                            Some(false) => NullableInterval::FALSE,
    72
    0
                            None => NullableInterval::UNKNOWN,
    73
                        },
    74
                        _ => {
    75
    0
                            let b = Expr::Literal(scalar.cast_to(&DataType::Boolean)?, None);
    76
    0
                            self.evaluate_bounds(&b)?
    77
                        }
    78
                    }
    79
                }
    80
    0
                Expr::IsNull(e) => {
    81
                    // If `e` is not nullable, then `e IS NULL` is provably false
    82
    0
                    if !e.nullable(self.input_schema)? {
    83
    0
                        NullableInterval::FALSE
    84
                    } else {
    85
    0
                        match e.get_type(self.input_schema)? {
    86
                            // If `e` is a boolean expression, check if `e` is provably 'unknown'.
    87
    0
                            DataType::Boolean => self.evaluate_bounds(e)?.is_unknown()?,
    88
                            // If `e` is not a boolean expression, check if `e` is provably null
    89
    0
                            _ => self.is_null(e),
    90
                        }
    91
                    }
    92
                }
    93
    0
                Expr::IsNotNull(e) => {
    94
                    // If `e` is not nullable, then `e IS NOT NULL` is provably true
    95
    0
                    if !e.nullable(self.input_schema)? {
    96
    0
                        NullableInterval::TRUE
    97
                    } else {
    98
    0
                        match e.get_type(self.input_schema)? {
    99
                            // If `e` is a boolean expression, try to evaluate it and test for not unknown
    100
                            DataType::Boolean => {
    101
    0
                                self.evaluate_bounds(e)?.is_unknown()?.not()?
    102
                            }
    103
                            // If `e` is not a boolean expression, check if `e` is provably null
    104
    0
                            _ => self.is_null(e).not()?,
    105
                        }
    106
                    }
    107
                }
    108
    0
                Expr::IsTrue(e) => self.evaluate_bounds(e)?.is_true()?,
    109
    0
                Expr::IsNotTrue(e) => self.evaluate_bounds(e)?.is_true()?.not()?,
    110
    0
                Expr::IsFalse(e) => self.evaluate_bounds(e)?.is_false()?,
    111
    0
                Expr::IsNotFalse(e) => self.evaluate_bounds(e)?.is_false()?.not()?,
    112
    0
                Expr::IsUnknown(e) => self.evaluate_bounds(e)?.is_unknown()?,
    113
    0
                Expr::IsNotUnknown(e) => self.evaluate_bounds(e)?.is_unknown()?.not()?,
    114
    0
                Expr::Not(e) => self.evaluate_bounds(e)?.not()?,
    115
                Expr::BinaryExpr(BinaryExpr {
    116
    0
                    left,
    117
                    op: Operator::And,
    118
    0
                    right,
    119
    0
                }) => NullableInterval::and(
    120
    0
                    &self.evaluate_bounds(left)?,
    121
    0
                    &self.evaluate_bounds(right)?,
    122
    0
                )?,
    123
                Expr::BinaryExpr(BinaryExpr {
    124
    0
                    left,
    125
                    op: Operator::Or,
    126
    0
                    right,
    127
    0
                }) => NullableInterval::or(
    128
    0
                    &self.evaluate_bounds(left)?,
    129
    0
                    &self.evaluate_bounds(right)?,
    130
    0
                )?,
    131
    0
                e => {
    132
    0
                    let is_null = self.is_null(e);
    133
    134
                    // If an expression is null, then it's value is UNKNOWN
    135
    0
                    let maybe_null =
    136
    0
                        is_null.contains_value(ScalarValue::Boolean(Some(true)))?;
    137
    138
    0
                    let maybe_not_null =
    139
    0
                        is_null.contains_value(ScalarValue::Boolean(Some(false)))?;
    140
    141
    0
                    match (maybe_null, maybe_not_null) {
    142
    0
                        (true, true) | (false, false) => NullableInterval::ANY_TRUTH_VALUE,
    143
    0
                        (true, false) => NullableInterval::UNKNOWN,
    144
    0
                        (false, true) => NullableInterval::TRUE_OR_FALSE,
    145
                    }
    146
                }
    147
            })
    148
    0
        }
    149
    150
        /// Determines if the given expression can evaluate to `NULL`.
    151
        ///
    152
        /// This method only returns sets containing `TRUE`, `FALSE`, or both.
    153
    0
        fn is_null(&self, expr: &Expr) -> NullableInterval {
    154
            // Fast path for literals
    155
    0
            if let Expr::Literal(scalar, _) = expr {
    156
    0
                if scalar.is_null() {
    157
    0
                    return NullableInterval::TRUE;
    158
                } else {
    159
    0
                    return NullableInterval::FALSE;
    160
                }
    161
    0
            }
    162
    163
            // If `expr` is not nullable, we can be certain `expr` is not null
    164
    0
            if let Ok(false) = expr.nullable(self.input_schema) {
    165
    0
                return NullableInterval::FALSE;
    166
    0
            }
    167
    168
            // `expr` is nullable, so our default answer for `is null` is going to be `{ TRUE, FALSE }`.
    169
            // Try to see if we can narrow it down to just one option.
    170
    0
            match expr {
    171
    0
                Expr::BinaryExpr(BinaryExpr { op, .. }) if op.returns_null_on_null() => {
    172
    0
                    self.is_null_if_any_child_null(expr)
    173
                }
    174
                Expr::Alias(_)
    175
                | Expr::Cast(_)
    176
                | Expr::Like(_)
    177
                | Expr::Negative(_)
    178
                | Expr::Not(_)
    179
    0
                | Expr::SimilarTo(_) => self.is_null_if_any_child_null(expr),
    180
                Expr::Between(Between {
    181
    0
                    expr, low, high, ..
    182
    0
                }) if self.is_null(expr).is_certainly_true()
    183
    0
                    || (self.is_null(low.as_ref()).is_certainly_true()
    184
    0
                        && self.is_null(high.as_ref()).is_certainly_true()) =>
    185
                {
    186
                    // Between is always null if the left side is null
    187
                    // or both the low and high bounds are null
    188
    0
                    NullableInterval::TRUE
    189
                }
    190
    0
                _ => NullableInterval::TRUE_OR_FALSE,
    191
            }
    192
    0
        }
    193
    194
    0
        fn is_null_if_any_child_null(&self, expr: &Expr) -> NullableInterval {
    195
            // These expressions are null if any of their direct children is null
    196
            // If any child is inconclusive, the result for this expression is also inconclusive
    197
    0
            let mut is_null = NullableInterval::FALSE;
    198
    199
    0
            let _ = expr.apply_children(|child| {
    200
    0
                let child_is_null = self.is_null(child);
    201
    202
    0
                if child_is_null.contains_value(ScalarValue::Boolean(Some(true)))? {
    203
    0
                    // If a child might be null, then the result may also be null
    204
    0
                    is_null = NullableInterval::TRUE_OR_FALSE;
    205
    0
                }
    206
    207
    0
                if !child_is_null.contains_value(ScalarValue::Boolean(Some(false)))? {
    208
                    // If the child is never not null, then the result can also never be not null
    209
                    // and we can stop traversing the children
    210
    0
                    is_null = NullableInterval::TRUE;
    211
    0
                    Ok(TreeNodeRecursion::Stop)
    212
                } else {
    213
    0
                    Ok(TreeNodeRecursion::Continue)
    214
                }
    215
    0
            });
    216
    217
    0
            is_null
    218
    0
        }
    219
    }
    220
    221
    #[cfg(test)]
    222
    mod tests {
    223
        use crate::expr::ScalarFunction;
    224
        use crate::predicate_bounds::evaluate_bounds;
    225
        use crate::{
    226
            binary_expr, col, create_udf, is_false, is_not_false, is_not_null, is_not_true,
    227
            is_not_unknown, is_null, is_true, is_unknown, lit, not, Expr,
    228
        };
    229
        use arrow::datatypes::{DataType, Field, Schema};
    230
        use datafusion_common::{DFSchema, Result, ScalarValue};
    231
        use datafusion_expr_common::columnar_value::ColumnarValue;
    232
        use datafusion_expr_common::interval_arithmetic::NullableInterval;
    233
        use datafusion_expr_common::operator::Operator::{And, Eq, Or};
    234
        use datafusion_expr_common::signature::Volatility;
    235
        use std::ops::Neg;
    236
        use std::sync::Arc;
    237
    238
        fn eval_bounds(predicate: &Expr) -> Result<NullableInterval> {
    239
            let schema = DFSchema::try_from(Schema::empty())?;
    240
            evaluate_bounds(predicate, &schema)
    241
        }
    242
    243
        #[test]
    244
        fn evaluate_bounds_literal() {
    245
            #[rustfmt::skip]
    246
            let cases = vec![
    247
                (lit(ScalarValue::Null), NullableInterval::UNKNOWN),
    248
                (lit(false), NullableInterval::FALSE),
    249
                (lit(true), NullableInterval::TRUE),
    250
                (lit(0), NullableInterval::FALSE),
    251
                (lit(1), NullableInterval::TRUE),
    252
                (lit(ScalarValue::Utf8(None)), NullableInterval::UNKNOWN),
    253
            ];
    254
    255
            for case in cases {
    256
                assert_eq!(
    257
                    eval_bounds(&case.0).unwrap(),
    258
                    case.1,
    259
                    "Failed for {}",
    260
                    case.0
    261
                );
    262
            }
    263
    264
            assert!(eval_bounds(&lit("foo")).is_err());
    265
        }
    266
    267
        #[test]
    268
        fn evaluate_bounds_and() {
    269
            let null = lit(ScalarValue::Null);
    270
            let zero = lit(0);
    271
            let one = lit(1);
    272
            let t = lit(true);
    273
            let f = lit(false);
    274
            let func = make_scalar_func_expr();
    275
    276
            #[rustfmt::skip]
    277
            let cases = vec![
    278
                (binary_expr(null.clone(), And, null.clone()), NullableInterval::UNKNOWN),
    279
                (binary_expr(null.clone(), And, one.clone()), NullableInterval::UNKNOWN),
    280
                (binary_expr(null.clone(), And, zero.clone()), NullableInterval::FALSE),
    281
                (binary_expr(one.clone(), And, one.clone()), NullableInterval::TRUE),
    282
                (binary_expr(one.clone(), And, zero.clone()), NullableInterval::FALSE),
    283
                (binary_expr(null.clone(), And, t.clone()), NullableInterval::UNKNOWN),
    284
                (binary_expr(t.clone(), And, null.clone()), NullableInterval::UNKNOWN),
    285
                (binary_expr(null.clone(), And, f.clone()), NullableInterval::FALSE),
    286
                (binary_expr(f.clone(), And, null.clone()), NullableInterval::FALSE),
    287
                (binary_expr(t.clone(), And, t.clone()), NullableInterval::TRUE),
    288
                (binary_expr(t.clone(), And, f.clone()), NullableInterval::FALSE),
    289
                (binary_expr(f.clone(), And, t.clone()), NullableInterval::FALSE),
    290
                (binary_expr(f.clone(), And, f.clone()), NullableInterval::FALSE),
    291
                (binary_expr(t.clone(), And, func.clone()), NullableInterval::ANY_TRUTH_VALUE),
    292
                (binary_expr(func.clone(), And, t.clone()), NullableInterval::ANY_TRUTH_VALUE),
    293
                (binary_expr(f.clone(), And, func.clone()), NullableInterval::FALSE),
    294
                (binary_expr(func.clone(), And, f.clone()), NullableInterval::FALSE),
    295
                (binary_expr(null.clone(), And, func.clone()), NullableInterval::FALSE_OR_UNKNOWN),
    296
                (binary_expr(func.clone(), And, null.clone()), NullableInterval::FALSE_OR_UNKNOWN),
    297
            ];
    298
    299
            for case in cases {
    300
                assert_eq!(
    301
                    eval_bounds(&case.0).unwrap(),
    302
                    case.1,
    303
                    "Failed for {}",
    304
                    case.0
    305
                );
    306
            }
    307
        }
    308
    309
        #[test]
    310
        fn evaluate_bounds_or() {
    311
            let null = lit(ScalarValue::Null);
    312
            let zero = lit(0);
    313
            let one = lit(1);
    314
            let t = lit(true);
    315
            let f = lit(false);
    316
            let func = make_scalar_func_expr();
    317
    318
            #[rustfmt::skip]
    319
            let cases = vec![
    320
                (binary_expr(null.clone(), Or, null.clone()), NullableInterval::UNKNOWN),
    321
                (binary_expr(null.clone(), Or, one.clone()), NullableInterval::TRUE),
    322
                (binary_expr(null.clone(), Or, zero.clone()), NullableInterval::UNKNOWN),
    323
                (binary_expr(one.clone(), Or, one.clone()), NullableInterval::TRUE),
    324
                (binary_expr(one.clone(), Or, zero.clone()), NullableInterval::TRUE),
    325
                (binary_expr(null.clone(), Or, t.clone()), NullableInterval::TRUE),
    326
                (binary_expr(t.clone(), Or, null.clone()), NullableInterval::TRUE),
    327
                (binary_expr(null.clone(), Or, f.clone()), NullableInterval::UNKNOWN),
    328
                (binary_expr(f.clone(), Or, null.clone()), NullableInterval::UNKNOWN),
    329
                (binary_expr(t.clone(), Or, t.clone()), NullableInterval::TRUE),
    330
                (binary_expr(t.clone(), Or, f.clone()), NullableInterval::TRUE),
    331
                (binary_expr(f.clone(), Or, t.clone()), NullableInterval::TRUE),
    332
                (binary_expr(f.clone(), Or, f.clone()), NullableInterval::FALSE),
    333
                (binary_expr(t.clone(), Or, func.clone()), NullableInterval::TRUE),
    334
                (binary_expr(func.clone(), Or, t.clone()), NullableInterval::TRUE),
    335
                (binary_expr(f.clone(), Or, func.clone()), NullableInterval::ANY_TRUTH_VALUE),
    336
                (binary_expr(func.clone(), Or, f.clone()), NullableInterval::ANY_TRUTH_VALUE),
    337
                (binary_expr(null.clone(), Or, func.clone()), NullableInterval::TRUE_OR_UNKNOWN),
    338
                (binary_expr(func.clone(), Or, null.clone()), NullableInterval::TRUE_OR_UNKNOWN),
    339
            ];
    340
    341
            for case in cases {
    342
                assert_eq!(
    343
                    eval_bounds(&case.0).unwrap(),
    344
                    case.1,
    345
                    "Failed for {}",
    346
                    case.0
    347
                );
    348
            }
    349
        }
    350
    351
        #[test]
    352
        fn evaluate_bounds_not() {
    353
            let null = lit(ScalarValue::Null);
    354
            let zero = lit(0);
    355
            let one = lit(1);
    356
            let t = lit(true);
    357
            let f = lit(false);
    358
            let func = make_scalar_func_expr();
    359
    360
            #[rustfmt::skip]
    361
            let cases = vec![
    362
                (not(null.clone()), NullableInterval::UNKNOWN),
    363
                (not(one.clone()), NullableInterval::FALSE),
    364
                (not(zero.clone()), NullableInterval::TRUE),
    365
                (not(t.clone()), NullableInterval::FALSE),
    366
                (not(f.clone()), NullableInterval::TRUE),
    367
                (not(func.clone()), NullableInterval::ANY_TRUTH_VALUE),
    368
            ];
    369
    370
            for case in cases {
    371
                assert_eq!(
    372
                    eval_bounds(&case.0).unwrap(),
    373
                    case.1,
    374
                    "Failed for {}",
    375
                    case.0
    376
                );
    377
            }
    378
        }
    379
    380
        #[test]
    381
        fn evaluate_bounds_is() {
    382
            let null = lit(ScalarValue::Null);
    383
            let zero = lit(0);
    384
            let one = lit(1);
    385
            let t = lit(true);
    386
            let f = lit(false);
    387
            let col = col("col");
    388
            let nullable_schema = DFSchema::try_from(Schema::new(vec![Field::new(
    389
                "col",
    390
                DataType::UInt8,
    391
                true,
    392
            )]))
    393
            .unwrap();
    394
            let not_nullable_schema = DFSchema::try_from(Schema::new(vec![Field::new(
    395
                "col",
    396
                DataType::UInt8,
    397
                false,
    398
            )]))
    399
            .unwrap();
    400
    401
            #[rustfmt::skip]
    402
            let cases = vec![
    403
                (is_null(null.clone()), NullableInterval::TRUE),
    404
                (is_null(one.clone()), NullableInterval::FALSE),
    405
                (is_null(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::TRUE),
    406
                (is_not_null(null.clone()), NullableInterval::FALSE),
    407
                (is_not_null(one.clone()), NullableInterval::TRUE),
    408
                (is_not_null(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::FALSE),
    409
                (is_true(null.clone()), NullableInterval::FALSE),
    410
                (is_true(t.clone()), NullableInterval::TRUE),
    411
                (is_true(f.clone()), NullableInterval::FALSE),
    412
                (is_true(zero.clone()), NullableInterval::FALSE),
    413
                (is_true(one.clone()), NullableInterval::TRUE),
    414
                (is_true(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::FALSE),
    415
                (is_not_true(null.clone()), NullableInterval::TRUE),
    416
                (is_not_true(t.clone()), NullableInterval::FALSE),
    417
                (is_not_true(f.clone()), NullableInterval::TRUE),
    418
                (is_not_true(zero.clone()), NullableInterval::TRUE),
    419
                (is_not_true(one.clone()), NullableInterval::FALSE),
    420
                (is_not_true(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::TRUE),
    421
                (is_false(null.clone()), NullableInterval::FALSE),
    422
                (is_false(t.clone()), NullableInterval::FALSE),
    423
                (is_false(f.clone()), NullableInterval::TRUE),
    424
                (is_false(zero.clone()), NullableInterval::TRUE),
    425
                (is_false(one.clone()), NullableInterval::FALSE),
    426
                (is_false(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::FALSE),
    427
                (is_not_false(null.clone()), NullableInterval::TRUE),
    428
                (is_not_false(t.clone()), NullableInterval::TRUE),
    429
                (is_not_false(f.clone()), NullableInterval::FALSE),
    430
                (is_not_false(zero.clone()), NullableInterval::FALSE),
    431
                (is_not_false(one.clone()), NullableInterval::TRUE),
    432
                (is_not_false(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::TRUE),
    433
                (is_unknown(null.clone()), NullableInterval::TRUE),
    434
                (is_unknown(t.clone()), NullableInterval::FALSE),
    435
                (is_unknown(f.clone()), NullableInterval::FALSE),
    436
                (is_unknown(zero.clone()), NullableInterval::FALSE),
    437
                (is_unknown(one.clone()), NullableInterval::FALSE),
    438
                (is_unknown(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::TRUE),
    439
                (is_not_unknown(null.clone()), NullableInterval::FALSE),
    440
                (is_not_unknown(t.clone()), NullableInterval::TRUE),
    441
                (is_not_unknown(f.clone()), NullableInterval::TRUE),
    442
                (is_not_unknown(zero.clone()), NullableInterval::TRUE),
    443
                (is_not_unknown(one.clone()), NullableInterval::TRUE),
    444
                (is_not_unknown(binary_expr(null.clone(), Eq, null.clone())), NullableInterval::FALSE),
    445
            ];
    446
    447
            for case in cases {
    448
                assert_eq!(
    449
                    eval_bounds(&case.0).unwrap(),
    450
                    case.1,
    451
                    "Failed for {}",
    452
                    case.0
    453
                );
    454
            }
    455
    456
            #[rustfmt::skip]
    457
            let cases = vec![
    458
                (is_null(col.clone()), &nullable_schema, NullableInterval::TRUE_OR_FALSE),
    459
                (is_null(col.clone()), &not_nullable_schema, NullableInterval::FALSE),
    460
                (is_null(binary_expr(col.clone(), Eq, col.clone())), &nullable_schema, NullableInterval::TRUE_OR_FALSE),
    461
                (is_null(binary_expr(col.clone(), Eq, col.clone())), &not_nullable_schema, NullableInterval::FALSE),
    462
                (is_not_null(col.clone()), &nullable_schema, NullableInterval::TRUE_OR_FALSE),
    463
                (is_not_null(col.clone()), &not_nullable_schema, NullableInterval::TRUE),
    464
                (is_not_null(binary_expr(col.clone(), Eq, col.clone())), &nullable_schema, NullableInterval::TRUE_OR_FALSE),
    465
                (is_not_null(binary_expr(col.clone(), Eq, col.clone())), &not_nullable_schema, NullableInterval::TRUE),
    466
            ];
    467
    468
            for case in cases {
    469
                assert_eq!(
    470
                    evaluate_bounds(&case.0, case.1).unwrap(),
    471
                    case.2,
    472
                    "Failed for {}",
    473
                    case.0
    474
                );
    475
            }
    476
        }
    477
    478
        #[test]
    479
        fn evaluate_bounds_between() {
    480
            let null = lit(ScalarValue::Null);
    481
            let zero = lit(0);
    482
    483
            #[rustfmt::skip]
    484
            let cases = vec![
    485
                (zero.clone().between(zero.clone(), zero.clone()), NullableInterval::TRUE_OR_FALSE),
    486
                (null.clone().between(zero.clone(), zero.clone()), NullableInterval::UNKNOWN),
    487
                (zero.clone().between(null.clone(), zero.clone()), NullableInterval::ANY_TRUTH_VALUE),
    488
                (zero.clone().between(zero.clone(), null.clone()), NullableInterval::ANY_TRUTH_VALUE),
    489
                (zero.clone().between(null.clone(), null.clone()), NullableInterval::UNKNOWN),
    490
                (null.clone().between(null.clone(), null.clone()), NullableInterval::UNKNOWN),
    491
            ];
    492
    493
            for case in cases {
    494
                assert_eq!(
    495
                    eval_bounds(&case.0).unwrap(),
    496
                    case.1,
    497
                    "Failed for {}",
    498
                    case.0
    499
                );
    500
            }
    501
        }
    502
    503
        #[test]
    504
        fn evaluate_bounds_binary_op() {
    505
            let null = lit(ScalarValue::Null);
    506
            let zero = lit(0);
    507
            let col = col("col");
    508
            let nullable_schema = DFSchema::try_from(Schema::new(vec![Field::new(
    509
                "col",
    510
                DataType::Utf8,
    511
                true,
    512
            )]))
    513
            .unwrap();
    514
            let not_nullable_schema = DFSchema::try_from(Schema::new(vec![Field::new(
    515
                "col",
    516
                DataType::Utf8,
    517
                false,
    518
            )]))
    519
            .unwrap();
    520
    521
            #[rustfmt::skip]
    522
            let cases = vec![
    523
                (binary_expr(zero.clone(), Eq, zero.clone()), NullableInterval::TRUE_OR_FALSE),
    524
                (binary_expr(null.clone(), Eq, zero.clone()), NullableInterval::UNKNOWN),
    525
                (binary_expr(zero.clone(), Eq, null.clone()), NullableInterval::UNKNOWN),
    526
                (binary_expr(null.clone(), Eq, null.clone()), NullableInterval::UNKNOWN),
    527
            ];
    528
    529
            for case in cases {
    530
                assert_eq!(
    531
                    eval_bounds(&case.0).unwrap(),
    532
                    case.1,
    533
                    "Failed for {}",
    534
                    case.0
    535
                );
    536
            }
    537
    538
            #[rustfmt::skip]
    539
            let cases = vec![
    540
                (binary_expr(zero.clone(), Eq, col.clone()), NullableInterval::TRUE_OR_FALSE),
    541
                (binary_expr(col.clone(), Eq, zero.clone()), NullableInterval::TRUE_OR_FALSE),
    542
            ];
    543
    544
            for case in cases {
    545
                assert_eq!(
    546
                    evaluate_bounds(&case.0, &not_nullable_schema).unwrap(),
    547
                    case.1,
    548
                    "Failed for {}",
    549
                    case.0
    550
                );
    551
    552
                assert_eq!(
    553
                    evaluate_bounds(&case.0, &nullable_schema).unwrap(),
    554
                    NullableInterval::ANY_TRUTH_VALUE,
    555
                    "Failed for {}",
    556
                    case.0
    557
                );
    558
            }
    559
        }
    560
    561
        #[test]
    562
        fn evaluate_bounds_negative() {
    563
            let null = lit(ScalarValue::Null);
    564
            let zero = lit(0);
    565
    566
            #[rustfmt::skip]
    567
            let cases = vec![
    568
                (zero.clone().neg(), NullableInterval::TRUE_OR_FALSE),
    569
                (null.clone().neg(), NullableInterval::UNKNOWN),
    570
            ];
    571
    572
            for case in cases {
    573
                assert_eq!(
    574
                    eval_bounds(&case.0).unwrap(),
    575
                    case.1,
    576
                    "Failed for {}",
    577
                    case.0
    578
                );
    579
            }
    580
        }
    581
    582
        #[test]
    583
        fn evaluate_bounds_like() {
    584
            let null = lit(ScalarValue::Null);
    585
            let expr = lit("foo");
    586
            let pattern = lit("f.*");
    587
            let col = col("col");
    588
            let nullable_schema = DFSchema::try_from(Schema::new(vec![Field::new(
    589
                "col",
    590
                DataType::Utf8,
    591
                true,
    592
            )]))
    593
            .unwrap();
    594
            let not_nullable_schema = DFSchema::try_from(Schema::new(vec![Field::new(
    595
                "col",
    596
                DataType::Utf8,
    597
                false,
    598
            )]))
    599
            .unwrap();
    600
    601
            #[rustfmt::skip]
    602
            let cases = vec![
    603
                (expr.clone().like(pattern.clone()), NullableInterval::TRUE_OR_FALSE),
    604
                (null.clone().like(pattern.clone()), NullableInterval::UNKNOWN),
    605
                (expr.clone().like(null.clone()), NullableInterval::UNKNOWN),
    606
                (null.clone().like(null.clone()), NullableInterval::UNKNOWN),
    607
            ];
    608
    609
            for case in cases {
    610
                assert_eq!(
    611
                    eval_bounds(&case.0).unwrap(),
    612
                    case.1,
    613
                    "Failed for {}",
    614
                    case.0
    615
                );
    616
            }
    617
    618
            #[rustfmt::skip]
    619
            let cases = vec![
    620
                (col.clone().like(pattern.clone()), NullableInterval::TRUE_OR_FALSE),
    621
                (expr.clone().like(col.clone()), NullableInterval::TRUE_OR_FALSE),
    622
            ];
    623
    624
            for case in cases {
    625
                assert_eq!(
    626
                    evaluate_bounds(&case.0, &not_nullable_schema).unwrap(),
    627
                    case.1,
    628
                    "Failed for {}",
    629
                    case.0
    630
                );
    631
    632
                assert_eq!(
    633
                    evaluate_bounds(&case.0, &nullable_schema).unwrap(),
    634
                    NullableInterval::ANY_TRUTH_VALUE,
    635
                    "Failed for {}",
    636
                    case.0
    637
                );
    638
            }
    639
        }
    640
    641
        #[test]
    642
        fn evaluate_bounds_udf() {
    643
            let func = make_scalar_func_expr();
    644
    645
            #[rustfmt::skip]
    646
            let cases = vec![
    647
                (func.clone(), NullableInterval::ANY_TRUTH_VALUE),
    648
                (not(func.clone()), NullableInterval::ANY_TRUTH_VALUE),
    649
                (binary_expr(func.clone(), And, func.clone()), NullableInterval::ANY_TRUTH_VALUE),
    650
            ];
    651
    652
            for case in cases {
    653
                assert_eq!(eval_bounds(&case.0).unwrap(), case.1);
    654
            }
    655
        }
    656
    657
        fn make_scalar_func_expr() -> Expr {
    658
            let scalar_func_impl =
    659
                |_: &[ColumnarValue]| Ok(ColumnarValue::Scalar(ScalarValue::Null));
    660
            let udf = create_udf(
    661
                "foo",
    662
                vec![],
    663
                DataType::Boolean,
    664
                Volatility::Stable,
    665
                Arc::new(scalar_func_impl),
    666
            );
    667
            Expr::ScalarFunction(ScalarFunction::new_udf(Arc::new(udf), vec![]))
    668
        }
    669
    }