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