## 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/expressions/not.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
    //! Not expression
    19
    20
    use std::any::Any;
    21
    use std::fmt;
    22
    use std::hash::Hash;
    23
    use std::sync::Arc;
    24
    25
    use crate::PhysicalExpr;
    26
    27
    use arrow::datatypes::{DataType, FieldRef, Schema};
    28
    use arrow::record_batch::RecordBatch;
    29
    use datafusion_common::{cast::as_boolean_array, internal_err, Result, ScalarValue};
    30
    use datafusion_expr::interval_arithmetic::Interval;
    31
    use datafusion_expr::statistics::Distribution::{self, Bernoulli};
    32
    use datafusion_expr::ColumnarValue;
    33
    34
    /// Not expression
    35
    #[derive(Debug, Eq)]
    36
    pub struct NotExpr {
    37
        /// Input expression
    38
        arg: Arc<dyn PhysicalExpr>,
    39
    }
    40
    41
    // Manually derive PartialEq and Hash to work around https://github.com/rust-lang/rust/issues/78808
    42
    impl PartialEq for NotExpr {
    43
    0
        fn eq(&self, other: &Self) -> bool {
    44
    0
            self.arg.eq(&other.arg)
    45
    0
        }
    46
    }
    47
    48
    impl Hash for NotExpr {
    49
    0
        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
    50
    0
            self.arg.hash(state);
    51
    0
        }
    52
    }
    53
    54
    impl NotExpr {
    55
        /// Create new not expression
    56
    0
        pub fn new(arg: Arc<dyn PhysicalExpr>) -> Self {
    57
    0
            Self { arg }
    58
    0
        }
    59
    60
        /// Get the input expression
    61
    0
        pub fn arg(&self) -> &Arc<dyn PhysicalExpr> {
    62
    0
            &self.arg
    63
    0
        }
    64
    }
    65
    66
    impl fmt::Display for NotExpr {
    67
    0
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    68
    0
            write!(f, "NOT {}", self.arg)
    69
    0
        }
    70
    }
    71
    72
    impl PhysicalExpr for NotExpr {
    73
        /// Return a reference to Any that can be used for downcasting
    74
    0
        fn as_any(&self) -> &dyn Any {
    75
    0
            self
    76
    0
        }
    77
    78
    0
        fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
    79
    0
            Ok(DataType::Boolean)
    80
    0
        }
    81
    82
    0
        fn nullable(&self, input_schema: &Schema) -> Result<bool> {
    83
    0
            self.arg.nullable(input_schema)
    84
    0
        }
    85
    86
    0
        fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
    87
    0
            match self.arg.evaluate(batch)? {
    88
    0
                ColumnarValue::Array(array) => {
    89
    0
                    let array = as_boolean_array(&array)?;
    90
    0
                    Ok(ColumnarValue::Array(Arc::new(
    91
    0
                        arrow::compute::kernels::boolean::not(array)?,
    92
                    )))
    93
                }
    94
    0
                ColumnarValue::Scalar(scalar) => {
    95
    0
                    if scalar.is_null() {
    96
    0
                        return Ok(ColumnarValue::Scalar(ScalarValue::Boolean(None)));
    97
    0
                    }
    98
    0
                    let bool_value: bool = scalar.try_into()?;
    99
    0
                    Ok(ColumnarValue::Scalar(ScalarValue::from(!bool_value)))
    100
                }
    101
            }
    102
    0
        }
    103
    104
    0
        fn return_field(&self, input_schema: &Schema) -> Result<FieldRef> {
    105
    0
            self.arg.return_field(input_schema)
    106
    0
        }
    107
    108
    0
        fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
    109
    0
            vec![&self.arg]
    110
    0
        }
    111
    112
    0
        fn with_new_children(
    113
    0
            self: Arc<Self>,
    114
    0
            children: Vec<Arc<dyn PhysicalExpr>>,
    115
    0
        ) -> Result<Arc<dyn PhysicalExpr>> {
    116
    0
            Ok(Arc::new(NotExpr::new(Arc::clone(&children[0]))))
    117
    0
        }
    118
    119
    0
        fn evaluate_bounds(&self, children: &[&Interval]) -> Result<Interval> {
    120
    0
            children[0].not()
    121
    0
        }
    122
    123
    0
        fn propagate_constraints(
    124
    0
            &self,
    125
    0
            interval: &Interval,
    126
    0
            children: &[&Interval],
    127
    0
        ) -> Result<Option<Vec<Interval>>> {
    128
    0
            let complemented_interval = interval.not()?;
    129
    130
    0
            Ok(children[0]
    131
    0
                .intersect(complemented_interval)?
    132
    0
                .map(|result| vec![result]))
    133
    0
        }
    134
    135
    0
        fn evaluate_statistics(&self, children: &[&Distribution]) -> Result<Distribution> {
    136
    0
            match children[0] {
    137
    0
                Bernoulli(b) => {
    138
    0
                    let p_value = b.p_value();
    139
    0
                    if p_value.is_null() {
    140
    0
                        Ok(children[0].clone())
    141
                    } else {
    142
    0
                        let one = ScalarValue::new_one(&p_value.data_type())?;
    143
    0
                        Distribution::new_bernoulli(one.sub_checked(p_value)?)
    144
                    }
    145
                }
    146
    0
                _ => internal_err!("NotExpr can only operate on Boolean datatypes"),
    147
            }
    148
    0
        }
    149
    150
    0
        fn propagate_statistics(
    151
    0
            &self,
    152
    0
            parent: &Distribution,
    153
    0
            children: &[&Distribution],
    154
    0
        ) -> Result<Option<Vec<Distribution>>> {
    155
    0
            match (parent, children[0]) {
    156
    0
                (Bernoulli(parent), Bernoulli(child)) => {
    157
    0
                    let parent_range = parent.range();
    158
    0
                    let result = if parent_range == Interval::TRUE {
    159
    0
                        if child.range() == Interval::TRUE {
    160
    0
                            None
    161
                        } else {
    162
    0
                            Some(vec![Distribution::new_bernoulli(ScalarValue::new_zero(
    163
    0
                                &child.data_type(),
    164
    0
                            )?)?])
    165
                        }
    166
    0
                    } else if parent_range == Interval::FALSE {
    167
    0
                        if child.range() == Interval::FALSE {
    168
    0
                            None
    169
                        } else {
    170
    0
                            Some(vec![Distribution::new_bernoulli(ScalarValue::new_one(
    171
    0
                                &child.data_type(),
    172
    0
                            )?)?])
    173
                        }
    174
                    } else {
    175
    0
                        Some(vec![])
    176
                    };
    177
    0
                    Ok(result)
    178
                }
    179
    0
                _ => internal_err!("NotExpr can only operate on Boolean datatypes"),
    180
            }
    181
    0
        }
    182
    183
    0
        fn fmt_sql(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    184
    0
            write!(f, "NOT ")?;
    185
    0
            self.arg.fmt_sql(f)
    186
    0
        }
    187
    }
    188
    189
    /// Creates a unary expression NOT
    190
    0
    pub fn not(arg: Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
    191
    0
        Ok(Arc::new(NotExpr::new(arg)))
    192
    0
    }
    193
    194
    #[cfg(test)]
    195
    mod tests {
    196
        use std::sync::LazyLock;
    197
    198
        use super::*;
    199
        use crate::expressions::{col, Column};
    200
    201
        use arrow::{array::BooleanArray, datatypes::*};
    202
        use datafusion_physical_expr_common::physical_expr::fmt_sql;
    203
    204
        #[test]
    205
    0
        fn neg_op() -> Result<()> {
    206
    0
            let schema = schema();
    207
    208
    0
            let expr = not(col("a", &schema)?)?;
    209
    0
            assert_eq!(expr.data_type(&schema)?, DataType::Boolean);
    210
    0
            assert!(expr.nullable(&schema)?);
    211
    212
    0
            let input = BooleanArray::from(vec![Some(true), None, Some(false)]);
    213
    0
            let expected = &BooleanArray::from(vec![Some(false), None, Some(true)]);
    214
    215
    0
            let batch = RecordBatch::try_new(schema, vec![Arc::new(input)])?;
    216
    217
    0
            let result = expr
    218
    0
                .evaluate(&batch)?
    219
    0
                .into_array(batch.num_rows())
    220
    0
                .expect("Failed to convert to array");
    221
    0
            let result =
    222
    0
                as_boolean_array(&result).expect("failed to downcast to BooleanArray");
    223
    0
            assert_eq!(result, expected);
    224
    225
    0
            Ok(())
    226
    0
        }
    227
    228
        #[test]
    229
    0
        fn test_evaluate_bounds() -> Result<()> {
    230
            // Note that `None` for boolean intervals is converted to `Some(false)`
    231
            // / `Some(true)` by `Interval::make`, so it is not explicitly tested
    232
            // here
    233
    234
            // if the bounds are all booleans (false, true) so is the negation
    235
    0
            assert_evaluate_bounds(
    236
    0
                Interval::make(Some(false), Some(true))?,
    237
    0
                Interval::make(Some(false), Some(true))?,
    238
    0
            )?;
    239
            // (true, false) is not tested because it is not a valid interval (lower
    240
            // bound is greater than upper bound)
    241
    0
            assert_evaluate_bounds(
    242
    0
                Interval::make(Some(true), Some(true))?,
    243
    0
                Interval::make(Some(false), Some(false))?,
    244
    0
            )?;
    245
    0
            assert_evaluate_bounds(
    246
    0
                Interval::make(Some(false), Some(false))?,
    247
    0
                Interval::make(Some(true), Some(true))?,
    248
    0
            )?;
    249
    0
            Ok(())
    250
    0
        }
    251
    252
    0
        fn assert_evaluate_bounds(
    253
    0
            interval: Interval,
    254
    0
            expected_interval: Interval,
    255
    0
        ) -> Result<()> {
    256
    0
            let not_expr = not(col("a", &schema())?)?;
    257
    0
            assert_eq!(not_expr.evaluate_bounds(&[&interval])?, expected_interval);
    258
    0
            Ok(())
    259
    0
        }
    260
    261
        #[test]
    262
    0
        fn test_evaluate_statistics() -> Result<()> {
    263
    0
            let _schema = &Schema::new(vec![Field::new("a", DataType::Boolean, false)]);
    264
    0
            let a = Arc::new(Column::new("a", 0)) as _;
    265
    0
            let expr = not(a)?;
    266
    267
            // Uniform with non-boolean bounds
    268
    0
            assert!(expr
    269
    0
                .evaluate_statistics(&[&Distribution::new_uniform(
    270
    0
                    Interval::make_unbounded(&DataType::Float64)?
    271
    0
                )?])
    272
    0
                .is_err());
    273
    274
            // Exponential
    275
    0
            assert!(expr
    276
    0
                .evaluate_statistics(&[&Distribution::new_exponential(
    277
    0
                    ScalarValue::from(1.0),
    278
    0
                    ScalarValue::from(1.0),
    279
                    true
    280
    0
                )?])
    281
    0
                .is_err());
    282
    283
            // Gaussian
    284
    0
            assert!(expr
    285
    0
                .evaluate_statistics(&[&Distribution::new_gaussian(
    286
    0
                    ScalarValue::from(1.0),
    287
    0
                    ScalarValue::from(1.0),
    288
    0
                )?])
    289
    0
                .is_err());
    290
    291
            // Bernoulli
    292
    0
            assert_eq!(
    293
    0
                expr.evaluate_statistics(&[&Distribution::new_bernoulli(
    294
    0
                    ScalarValue::from(0.0),
    295
    0
                )?])?,
    296
    0
                Distribution::new_bernoulli(ScalarValue::from(1.))?
    297
            );
    298
    299
    0
            assert_eq!(
    300
    0
                expr.evaluate_statistics(&[&Distribution::new_bernoulli(
    301
    0
                    ScalarValue::from(1.0),
    302
    0
                )?])?,
    303
    0
                Distribution::new_bernoulli(ScalarValue::from(0.))?
    304
            );
    305
    306
    0
            assert_eq!(
    307
    0
                expr.evaluate_statistics(&[&Distribution::new_bernoulli(
    308
    0
                    ScalarValue::from(0.25),
    309
    0
                )?])?,
    310
    0
                Distribution::new_bernoulli(ScalarValue::from(0.75))?
    311
            );
    312
    313
    0
            assert!(expr
    314
    0
                .evaluate_statistics(&[&Distribution::new_generic(
    315
    0
                    ScalarValue::Null,
    316
    0
                    ScalarValue::Null,
    317
    0
                    ScalarValue::Null,
    318
    0
                    Interval::make_unbounded(&DataType::UInt8)?
    319
    0
                )?])
    320
    0
                .is_err());
    321
    322
            // Unknown with non-boolean interval as range
    323
    0
            assert!(expr
    324
    0
                .evaluate_statistics(&[&Distribution::new_generic(
    325
    0
                    ScalarValue::Null,
    326
    0
                    ScalarValue::Null,
    327
    0
                    ScalarValue::Null,
    328
    0
                    Interval::make_unbounded(&DataType::Float64)?
    329
    0
                )?])
    330
    0
                .is_err());
    331
    332
    0
            Ok(())
    333
    0
        }
    334
    335
        #[test]
    336
    0
        fn test_fmt_sql() -> Result<()> {
    337
    0
            let schema = schema();
    338
    339
    0
            let expr = not(col("a", &schema)?)?;
    340
    341
    0
            let display_string = expr.to_string();
    342
    0
            assert_eq!(display_string, "NOT a@0");
    343
    344
    0
            let sql_string = fmt_sql(expr.as_ref()).to_string();
    345
    0
            assert_eq!(sql_string, "NOT a");
    346
    347
    0
            Ok(())
    348
    0
        }
    349
    350
    0
        fn schema() -> SchemaRef {
    351
    0
            static SCHEMA: LazyLock<SchemaRef> = LazyLock::new(|| {
    352
    0
                Arc::new(Schema::new(vec![Field::new("a", DataType::Boolean, true)]))
    353
    0
            });
    354
    0
            Arc::clone(&SCHEMA)
    355
    0
        }
    356
    }