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