vortex_array/scalar_fn/fns/
is_null.rs1use vortex_error::VortexExpect as _;
5use vortex_error::VortexResult;
6use vortex_session::VortexSession;
7use vortex_session::registry::CachedId;
8
9use crate::ArrayRef;
10use crate::ExecutionCtx;
11use crate::IntoArray;
12use crate::arrays::ConstantArray;
13use crate::arrays::ScalarFnArray;
14use crate::builtins::ArrayBuiltins;
15use crate::dtype::DType;
16use crate::dtype::Nullability;
17use crate::scalar_fn::Arity;
18use crate::scalar_fn::ChildName;
19use crate::scalar_fn::EmptyOptions;
20use crate::scalar_fn::ExecutionArgs;
21use crate::scalar_fn::ScalarFnId;
22use crate::scalar_fn::ScalarFnVTable;
23use crate::scalar_fn::ScalarFnVTableExt;
24use crate::validity::Validity;
25
26#[derive(Clone)]
28pub struct IsNull;
29
30impl IsNull {
31 #[expect(clippy::new_ret_no_self, reason = "constructs the lazy result array")]
33 pub fn new(input: ArrayRef) -> ScalarFnArray {
34 ScalarFnArray::try_new(IsNull.bind(EmptyOptions), vec![input])
35 .vortex_expect("IsNull has one child and an infallible return dtype")
36 }
37}
38
39impl ScalarFnVTable for IsNull {
40 type Options = EmptyOptions;
41
42 fn id(&self) -> ScalarFnId {
43 static ID: CachedId = CachedId::new("vortex.is_null");
44 *ID
45 }
46
47 fn serialize(&self, _instance: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
48 Ok(Some(vec![]))
49 }
50
51 fn deserialize(
52 &self,
53 _metadata: &[u8],
54 _session: &VortexSession,
55 ) -> VortexResult<Self::Options> {
56 Ok(EmptyOptions)
57 }
58
59 fn arity(&self, _options: &Self::Options) -> Arity {
60 Arity::Exact(1)
61 }
62
63 fn child_name(&self, _instance: &Self::Options, child_idx: usize) -> ChildName {
64 match child_idx {
65 0 => ChildName::from("input"),
66 _ => unreachable!("Invalid child index {} for IsNull expression", child_idx),
67 }
68 }
69
70 fn return_dtype(&self, _options: &Self::Options, _arg_dtypes: &[DType]) -> VortexResult<DType> {
71 Ok(DType::Bool(Nullability::NonNullable))
72 }
73
74 fn execute(
75 &self,
76 _data: &Self::Options,
77 args: &dyn ExecutionArgs,
78 _ctx: &mut ExecutionCtx,
79 ) -> VortexResult<ArrayRef> {
80 let child = args.get(0)?;
81 if let Some(scalar) = child.as_constant() {
82 return Ok(ConstantArray::new(scalar.is_null(), args.row_count()).into_array());
83 }
84
85 match child.validity()? {
86 Validity::NonNullable | Validity::AllValid => {
87 Ok(ConstantArray::new(false, args.row_count()).into_array())
88 }
89 Validity::AllInvalid => Ok(ConstantArray::new(true, args.row_count()).into_array()),
90 Validity::Array(a) => a.not(),
91 }
92 }
93
94 fn is_strict(&self, _instance: &Self::Options) -> bool {
95 false
97 }
98
99 fn is_infallible(&self, _instance: &Self::Options) -> bool {
100 true
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use std::sync::LazyLock;
107
108 use vortex_buffer::buffer;
109 use vortex_error::VortexExpect as _;
110 use vortex_error::VortexResult;
111 use vortex_session::VortexSession;
112
113 use crate::IntoArray;
114 use crate::VortexSessionExecute;
115 use crate::array_session;
116 use crate::arrays::PrimitiveArray;
117 use crate::arrays::StructArray;
118 use crate::dtype::DType;
119 use crate::dtype::Nullability;
120 use crate::expr::col;
121 use crate::expr::eq;
122 use crate::expr::get_item;
123 use crate::expr::is_null;
124 use crate::expr::lit;
125 use crate::expr::or;
126 use crate::expr::root;
127 use crate::expr::test_harness;
128 use crate::scalar::Scalar;
129 use crate::stats::StatsSession;
130 use crate::stats::all_non_null;
131 use crate::stats::null_count;
132
133 static STATS_SESSION: LazyLock<VortexSession> =
134 LazyLock::new(|| VortexSession::empty().with::<StatsSession>());
135
136 #[test]
137 fn dtype() {
138 let dtype = test_harness::struct_dtype();
139 assert_eq!(
140 is_null(root()).return_dtype(&dtype).unwrap(),
141 DType::Bool(Nullability::NonNullable)
142 );
143 }
144
145 #[test]
146 fn replace_children() {
147 let expr = is_null(root());
148 expr.with_children([root()])
149 .vortex_expect("operation should succeed in test");
150 }
151
152 #[test]
153 fn evaluate_mask() {
154 let test_array =
155 PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)])
156 .into_array();
157 let expected = [false, true, false, true, false];
158
159 let result = test_array.clone().apply(&is_null(root())).unwrap();
160
161 assert_eq!(result.len(), test_array.len());
162 assert_eq!(result.dtype(), &DType::Bool(Nullability::NonNullable));
163
164 for (i, expected_value) in expected.iter().enumerate() {
165 assert_eq!(
166 result
167 .execute_scalar(i, &mut array_session().create_execution_ctx())
168 .unwrap(),
169 Scalar::bool(*expected_value, Nullability::NonNullable)
170 );
171 }
172 }
173
174 #[test]
175 fn evaluate_all_false() {
176 let test_array = buffer![1, 2, 3, 4, 5].into_array();
177
178 let result = test_array.clone().apply(&is_null(root())).unwrap();
179
180 assert_eq!(result.len(), test_array.len());
181 for i in 0..result.len() {
183 assert_eq!(
184 result
185 .execute_scalar(i, &mut array_session().create_execution_ctx())
186 .unwrap(),
187 Scalar::bool(false, Nullability::NonNullable)
188 );
189 }
190 }
191
192 #[test]
193 fn evaluate_all_true() {
194 let test_array =
195 PrimitiveArray::from_option_iter(vec![None::<i32>, None, None, None, None])
196 .into_array();
197
198 let result = test_array.clone().apply(&is_null(root())).unwrap();
199
200 assert_eq!(result.len(), test_array.len());
201 for i in 0..result.len() {
203 assert_eq!(
204 result
205 .execute_scalar(i, &mut array_session().create_execution_ctx())
206 .unwrap(),
207 Scalar::bool(true, Nullability::NonNullable)
208 );
209 }
210 }
211
212 #[test]
213 fn evaluate_struct() {
214 let test_array = StructArray::from_fields(&[(
215 "a",
216 PrimitiveArray::from_option_iter(vec![Some(1), None, Some(2), None, Some(3)])
217 .into_array(),
218 )])
219 .unwrap()
220 .into_array();
221 let expected = [false, true, false, true, false];
222
223 let result = test_array
224 .clone()
225 .apply(&is_null(get_item("a", root())))
226 .unwrap();
227
228 assert_eq!(result.len(), test_array.len());
229 assert_eq!(result.dtype(), &DType::Bool(Nullability::NonNullable));
230
231 for (i, expected_value) in expected.iter().enumerate() {
232 assert_eq!(
233 result
234 .execute_scalar(i, &mut array_session().create_execution_ctx())
235 .unwrap(),
236 Scalar::bool(*expected_value, Nullability::NonNullable)
237 );
238 }
239 }
240
241 #[test]
242 fn test_display() {
243 let expr = is_null(get_item("name", root()));
244 assert_eq!(expr.to_string(), "vortex.is_null($.name)");
245
246 let expr2 = is_null(root());
247 assert_eq!(expr2.to_string(), "vortex.is_null($)");
248 }
249
250 #[test]
251 fn test_is_null_falsification() -> VortexResult<()> {
252 let expr = is_null(col("a"));
253 let dtype = test_harness::struct_dtype();
254
255 assert_eq!(
256 expr.bind(&dtype)?.falsify(&STATS_SESSION)?,
257 Some(or(eq(null_count(col("a")), lit(0u64)), all_non_null(col("a")),).bind(&dtype)?)
258 );
259 Ok(())
260 }
261
262 #[test]
263 fn test_is_null_is_not_strict() {
264 assert!(
265 !is_null(col("a"))
266 .as_scalar()
267 .is_some_and(|f| f.signature().is_strict())
268 );
269 }
270}