1use std::ops::Not;
5
6use vortex_array::arrays::{BoolArray, ConstantArray};
7use vortex_array::{Array, ArrayRef, DeserializeMetadata, EmptyMetadata, IntoArray};
8use vortex_dtype::{DType, Nullability};
9use vortex_error::{VortexResult, vortex_bail};
10use vortex_mask::Mask;
11
12use crate::display::{DisplayAs, DisplayFormat};
13use crate::{AnalysisExpr, ExprEncodingRef, ExprId, ExprRef, IntoExpr, Scope, VTable, vtable};
14
15vtable!(IsNull);
16
17#[allow(clippy::derived_hash_with_manual_eq)]
18#[derive(Clone, Debug, Hash, Eq)]
19pub struct IsNullExpr {
20 child: ExprRef,
21}
22
23impl PartialEq for IsNullExpr {
24 fn eq(&self, other: &Self) -> bool {
25 self.child.eq(&other.child)
26 }
27}
28
29pub struct IsNullExprEncoding;
30
31impl VTable for IsNullVTable {
32 type Expr = IsNullExpr;
33 type Encoding = IsNullExprEncoding;
34 type Metadata = EmptyMetadata;
35
36 fn id(_encoding: &Self::Encoding) -> ExprId {
37 ExprId::new_ref("is_null")
38 }
39
40 fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
41 ExprEncodingRef::new_ref(IsNullExprEncoding.as_ref())
42 }
43
44 fn metadata(_expr: &Self::Expr) -> Option<Self::Metadata> {
45 Some(EmptyMetadata)
46 }
47
48 fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
49 vec![&expr.child]
50 }
51
52 fn with_children(_expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
53 Ok(IsNullExpr::new(children[0].clone()))
54 }
55
56 fn build(
57 _encoding: &Self::Encoding,
58 _metadata: &<Self::Metadata as DeserializeMetadata>::Output,
59 children: Vec<ExprRef>,
60 ) -> VortexResult<Self::Expr> {
61 if children.len() != 1 {
62 vortex_bail!("IsNull expects exactly one child, got {}", children.len());
63 }
64 Ok(IsNullExpr::new(children[0].clone()))
65 }
66
67 fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
68 let array = expr.child.unchecked_evaluate(scope)?;
69 match array.validity_mask() {
70 Mask::AllTrue(len) => Ok(ConstantArray::new(false, len).into_array()),
71 Mask::AllFalse(len) => Ok(ConstantArray::new(true, len).into_array()),
72 Mask::Values(mask) => Ok(BoolArray::from(mask.boolean_buffer().not()).into_array()),
73 }
74 }
75
76 fn return_dtype(_expr: &Self::Expr, _scope: &DType) -> VortexResult<DType> {
77 Ok(DType::Bool(Nullability::NonNullable))
78 }
79}
80
81impl IsNullExpr {
82 pub fn new(child: ExprRef) -> Self {
83 Self { child }
84 }
85
86 pub fn new_expr(child: ExprRef) -> ExprRef {
87 Self::new(child).into_expr()
88 }
89}
90
91impl DisplayAs for IsNullExpr {
92 fn fmt_as(&self, df: DisplayFormat, f: &mut std::fmt::Formatter) -> std::fmt::Result {
93 match df {
94 DisplayFormat::Compact => {
95 write!(f, "is_null({})", self.child)
96 }
97 DisplayFormat::Tree => {
98 write!(f, "IsNull")
99 }
100 }
101 }
102}
103
104impl AnalysisExpr for IsNullExpr {}
105
106pub fn is_null(child: ExprRef) -> ExprRef {
115 IsNullExpr::new(child).into_expr()
116}
117
118#[cfg(test)]
119mod tests {
120 use vortex_array::IntoArray;
121 use vortex_array::arrays::{PrimitiveArray, StructArray};
122 use vortex_buffer::buffer;
123 use vortex_dtype::{DType, Nullability};
124 use vortex_scalar::Scalar;
125
126 use crate::is_null::is_null;
127 use crate::{Scope, get_item, root, test_harness};
128
129 #[test]
130 fn dtype() {
131 let dtype = test_harness::struct_dtype();
132 assert_eq!(
133 is_null(root()).return_dtype(&dtype).unwrap(),
134 DType::Bool(Nullability::NonNullable)
135 );
136 }
137
138 #[test]
139 fn replace_children() {
140 let expr = is_null(root());
141 let _ = expr.with_children(vec![root()]);
142 }
143
144 #[test]
145 fn evaluate_mask() {
146 let test_array =
147 PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)])
148 .into_array();
149 let expected = [false, true, false, true, false];
150
151 let result = is_null(root())
152 .evaluate(&Scope::new(test_array.clone()))
153 .unwrap();
154
155 assert_eq!(result.len(), test_array.len());
156 assert_eq!(result.dtype(), &DType::Bool(Nullability::NonNullable));
157
158 for (i, expected_value) in expected.iter().enumerate() {
159 assert_eq!(
160 result.scalar_at(i),
161 Scalar::bool(*expected_value, Nullability::NonNullable)
162 );
163 }
164 }
165
166 #[test]
167 fn evaluate_all_false() {
168 let test_array = buffer![1, 2, 3, 4, 5].into_array();
169
170 let result = is_null(root())
171 .evaluate(&Scope::new(test_array.clone()))
172 .unwrap();
173
174 assert_eq!(result.len(), test_array.len());
175 assert_eq!(
176 result.as_constant().unwrap(),
177 Scalar::bool(false, Nullability::NonNullable)
178 );
179 }
180
181 #[test]
182 fn evaluate_all_true() {
183 let test_array =
184 PrimitiveArray::from_option_iter(vec![None::<i32>, None, None, None, None])
185 .into_array();
186
187 let result = is_null(root())
188 .evaluate(&Scope::new(test_array.clone()))
189 .unwrap();
190
191 assert_eq!(result.len(), test_array.len());
192 assert_eq!(
193 result.as_constant().unwrap(),
194 Scalar::bool(true, Nullability::NonNullable)
195 );
196 }
197
198 #[test]
199 fn evaluate_struct() {
200 let test_array = StructArray::from_fields(&[(
201 "a",
202 PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)])
203 .into_array(),
204 )])
205 .unwrap()
206 .into_array();
207 let expected = [false, true, false, true, false];
208
209 let result = is_null(get_item("a", root()))
210 .evaluate(&Scope::new(test_array.clone()))
211 .unwrap();
212
213 assert_eq!(result.len(), test_array.len());
214 assert_eq!(result.dtype(), &DType::Bool(Nullability::NonNullable));
215
216 for (i, expected_value) in expected.iter().enumerate() {
217 assert_eq!(
218 result.scalar_at(i),
219 Scalar::bool(*expected_value, Nullability::NonNullable)
220 );
221 }
222 }
223
224 #[test]
225 fn test_display() {
226 let expr = is_null(get_item("name", root()));
227 assert_eq!(expr.to_string(), "is_null($.name)");
228
229 let expr2 = is_null(root());
230 assert_eq!(expr2.to_string(), "is_null($)");
231 }
232}