Coverage Report

Created: 2024-10-13 08:39

/Users/andrewlamb/Software/datafusion/datafusion/physical-expr/src/expressions/is_null.rs
Line
Count
Source (jump to first uncovered line)
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
//! IS NULL expression
19
20
use std::hash::{Hash, Hasher};
21
use std::{any::Any, sync::Arc};
22
23
use arrow::{
24
    datatypes::{DataType, Schema},
25
    record_batch::RecordBatch,
26
};
27
28
use crate::physical_expr::down_cast_any_ref;
29
use crate::PhysicalExpr;
30
use datafusion_common::Result;
31
use datafusion_common::ScalarValue;
32
use datafusion_expr::ColumnarValue;
33
34
/// IS NULL expression
35
#[derive(Debug, Hash)]
36
pub struct IsNullExpr {
37
    /// Input expression
38
    arg: Arc<dyn PhysicalExpr>,
39
}
40
41
impl IsNullExpr {
42
    /// Create new not expression
43
0
    pub fn new(arg: Arc<dyn PhysicalExpr>) -> Self {
44
0
        Self { arg }
45
0
    }
46
47
    /// Get the input expression
48
0
    pub fn arg(&self) -> &Arc<dyn PhysicalExpr> {
49
0
        &self.arg
50
0
    }
51
}
52
53
impl std::fmt::Display for IsNullExpr {
54
0
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
55
0
        write!(f, "{} IS NULL", self.arg)
56
0
    }
57
}
58
59
impl PhysicalExpr for IsNullExpr {
60
    /// Return a reference to Any that can be used for downcasting
61
0
    fn as_any(&self) -> &dyn Any {
62
0
        self
63
0
    }
64
65
0
    fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
66
0
        Ok(DataType::Boolean)
67
0
    }
68
69
0
    fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
70
0
        Ok(false)
71
0
    }
72
73
0
    fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
74
0
        let arg = self.arg.evaluate(batch)?;
75
0
        match arg {
76
0
            ColumnarValue::Array(array) => Ok(ColumnarValue::Array(Arc::new(
77
0
                arrow::compute::is_null(&array)?,
78
            ))),
79
0
            ColumnarValue::Scalar(scalar) => Ok(ColumnarValue::Scalar(
80
0
                ScalarValue::Boolean(Some(scalar.is_null())),
81
0
            )),
82
        }
83
0
    }
84
85
0
    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
86
0
        vec![&self.arg]
87
0
    }
88
89
0
    fn with_new_children(
90
0
        self: Arc<Self>,
91
0
        children: Vec<Arc<dyn PhysicalExpr>>,
92
0
    ) -> Result<Arc<dyn PhysicalExpr>> {
93
0
        Ok(Arc::new(IsNullExpr::new(Arc::clone(&children[0]))))
94
0
    }
95
96
0
    fn dyn_hash(&self, state: &mut dyn Hasher) {
97
0
        let mut s = state;
98
0
        self.hash(&mut s);
99
0
    }
100
}
101
102
impl PartialEq<dyn Any> for IsNullExpr {
103
0
    fn eq(&self, other: &dyn Any) -> bool {
104
0
        down_cast_any_ref(other)
105
0
            .downcast_ref::<Self>()
106
0
            .map(|x| self.arg.eq(&x.arg))
107
0
            .unwrap_or(false)
108
0
    }
109
}
110
111
/// Create an IS NULL expression
112
0
pub fn is_null(arg: Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
113
0
    Ok(Arc::new(IsNullExpr::new(arg)))
114
0
}
115
116
#[cfg(test)]
117
mod tests {
118
    use super::*;
119
    use crate::expressions::col;
120
    use arrow::{
121
        array::{BooleanArray, StringArray},
122
        datatypes::*,
123
    };
124
    use arrow_array::{Array, Float64Array, Int32Array, UnionArray};
125
    use arrow_buffer::ScalarBuffer;
126
    use datafusion_common::cast::as_boolean_array;
127
128
    #[test]
129
    fn is_null_op() -> Result<()> {
130
        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
131
        let a = StringArray::from(vec![Some("foo"), None]);
132
133
        // expression: "a is null"
134
        let expr = is_null(col("a", &schema)?).unwrap();
135
        let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)])?;
136
137
        let result = expr
138
            .evaluate(&batch)?
139
            .into_array(batch.num_rows())
140
            .expect("Failed to convert to array");
141
        let result =
142
            as_boolean_array(&result).expect("failed to downcast to BooleanArray");
143
144
        let expected = &BooleanArray::from(vec![false, true]);
145
146
        assert_eq!(expected, result);
147
148
        Ok(())
149
    }
150
151
    fn union_fields() -> UnionFields {
152
        [
153
            (0, Arc::new(Field::new("A", DataType::Int32, true))),
154
            (1, Arc::new(Field::new("B", DataType::Float64, true))),
155
            (2, Arc::new(Field::new("C", DataType::Utf8, true))),
156
        ]
157
        .into_iter()
158
        .collect()
159
    }
160
161
    #[test]
162
    fn sparse_union_is_null() {
163
        // union of [{A=1}, {A=}, {B=1.1}, {B=1.2}, {B=}, {C=}, {C="a"}]
164
        let int_array =
165
            Int32Array::from(vec![Some(1), None, None, None, None, None, None]);
166
        let float_array =
167
            Float64Array::from(vec![None, None, Some(1.1), Some(1.2), None, None, None]);
168
        let str_array =
169
            StringArray::from(vec![None, None, None, None, None, None, Some("a")]);
170
        let type_ids = [0, 0, 1, 1, 1, 2, 2]
171
            .into_iter()
172
            .collect::<ScalarBuffer<i8>>();
173
174
        let children = vec![
175
            Arc::new(int_array) as Arc<dyn Array>,
176
            Arc::new(float_array),
177
            Arc::new(str_array),
178
        ];
179
180
        let array =
181
            UnionArray::try_new(union_fields(), type_ids, None, children).unwrap();
182
183
        let result = arrow::compute::is_null(&array).unwrap();
184
185
        let expected =
186
            &BooleanArray::from(vec![false, true, false, false, true, true, false]);
187
        assert_eq!(expected, &result);
188
    }
189
190
    #[test]
191
    fn dense_union_is_null() {
192
        // union of [{A=1}, {A=}, {B=3.2}, {B=}, {C="a"}, {C=}]
193
        let int_array = Int32Array::from(vec![Some(1), None]);
194
        let float_array = Float64Array::from(vec![Some(3.2), None]);
195
        let str_array = StringArray::from(vec![Some("a"), None]);
196
        let type_ids = [0, 0, 1, 1, 2, 2].into_iter().collect::<ScalarBuffer<i8>>();
197
        let offsets = [0, 1, 0, 1, 0, 1]
198
            .into_iter()
199
            .collect::<ScalarBuffer<i32>>();
200
201
        let children = vec![
202
            Arc::new(int_array) as Arc<dyn Array>,
203
            Arc::new(float_array),
204
            Arc::new(str_array),
205
        ];
206
207
        let array =
208
            UnionArray::try_new(union_fields(), type_ids, Some(offsets), children)
209
                .unwrap();
210
211
        let result = arrow::compute::is_null(&array).unwrap();
212
213
        let expected = &BooleanArray::from(vec![false, true, false, true, false, true]);
214
        assert_eq!(expected, &result);
215
    }
216
}