1use num_traits::AsPrimitive;
5use vortex_error::VortexResult;
6use vortex_error::vortex_bail;
7use vortex_session::VortexSession;
8use vortex_session::registry::CachedId;
9
10use crate::ArrayRef;
11use crate::ExecutionCtx;
12use crate::IntoArray;
13use crate::array::ArrayView;
14use crate::arrays::ConstantArray;
15use crate::arrays::FixedSizeList;
16use crate::arrays::List;
17use crate::arrays::ListView;
18use crate::arrays::fixed_size_list::FixedSizeListArrayExt;
19use crate::arrays::list::ListArrayExt;
20use crate::arrays::list::ListArraySlotsExt;
21use crate::arrays::listview::ListViewArrayExt;
22use crate::arrays::listview::ListViewArraySlotsExt;
23use crate::builtins::ArrayBuiltins;
24use crate::dtype::DType;
25use crate::dtype::Nullability;
26use crate::dtype::PType;
27use crate::matcher::Matcher;
28use crate::scalar::Scalar;
29use crate::scalar_fn::Arity;
30use crate::scalar_fn::ChildName;
31use crate::scalar_fn::EmptyOptions;
32use crate::scalar_fn::ExecutionArgs;
33use crate::scalar_fn::ScalarFnId;
34use crate::scalar_fn::ScalarFnVTable;
35use crate::scalar_fn::fns::operators::Operator;
36
37#[derive(Clone)]
43pub struct ListLength;
44
45impl ScalarFnVTable for ListLength {
46 type Options = EmptyOptions;
47
48 fn id(&self) -> ScalarFnId {
49 static ID: CachedId = CachedId::new("vortex.list.length");
50 *ID
51 }
52
53 fn serialize(&self, _instance: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
54 Ok(Some(vec![]))
55 }
56
57 fn deserialize(
58 &self,
59 _metadata: &[u8],
60 _session: &VortexSession,
61 ) -> VortexResult<Self::Options> {
62 Ok(EmptyOptions)
63 }
64
65 fn arity(&self, _options: &Self::Options) -> Arity {
66 Arity::Exact(1)
67 }
68
69 fn child_name(&self, _instance: &Self::Options, child_idx: usize) -> ChildName {
70 match child_idx {
71 0 => ChildName::from("input"),
72 _ => unreachable!("Invalid child index {child_idx} for list_length()"),
73 }
74 }
75
76 fn return_dtype(&self, _options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
77 match &arg_dtypes[0] {
78 DType::List(_, nullable) | DType::FixedSizeList(_, _, nullable) => {
79 Ok(DType::Primitive(PType::U64, *nullable))
80 }
81 other => vortex_bail!("list_length() requires List or FixedSizeList, got {other}"),
82 }
83 }
84
85 fn execute(
86 &self,
87 _options: &Self::Options,
88 args: &dyn ExecutionArgs,
89 ctx: &mut ExecutionCtx,
90 ) -> VortexResult<ArrayRef> {
91 let input = args.get(0)?;
92 let nullability = input.dtype().nullability();
93
94 if let Some(scalar) = input.as_constant() {
95 let len_scalar = scalar_list_length(&scalar, nullability)?;
96 return Ok(ConstantArray::new(len_scalar, args.row_count()).into_array());
97 }
98
99 list_length(&input, nullability, ctx)
100 }
101
102 fn is_strict(&self, _options: &Self::Options) -> bool {
103 true
106 }
107
108 fn is_infallible(&self, _options: &Self::Options) -> bool {
109 true
110 }
111}
112
113fn scalar_list_length(scalar: &Scalar, nullability: Nullability) -> VortexResult<Scalar> {
114 if scalar.is_null() {
115 let dtype = DType::Primitive(PType::U64, Nullability::Nullable);
116 return Ok(Scalar::null(dtype));
117 }
118 let len: u64 = scalar.as_list().len().as_();
119 Ok(Scalar::primitive(len, nullability))
120}
121
122pub(crate) fn list_length(
123 array: &ArrayRef,
124 nullability: Nullability,
125 ctx: &mut ExecutionCtx,
126) -> VortexResult<ArrayRef> {
127 let any_list = array.clone().execute_until::<AnyList>(ctx)?;
128
129 let (lengths, validity) = if let Some(fsl) = any_list.as_opt::<FixedSizeList>() {
130 let size = fsl.list_size() as u64;
132 let lengths =
133 ConstantArray::new(Scalar::primitive(size, Nullability::NonNullable), fsl.len())
134 .into_array();
135 (lengths, fsl.validity()?)
136 } else if let Some(lv) = any_list.as_opt::<ListView>() {
137 (lv.sizes().clone(), lv.listview_validity())
139 } else if let Some(l) = any_list.as_opt::<List>() {
140 let lengths = list_length_from_offsets(l)?;
141 (lengths, l.list_validity())
142 } else {
143 let dtype = any_list.dtype();
144 vortex_bail!("list_length() requires List, ListView, or FixedSizeList but got {dtype}")
145 };
146
147 let len = lengths.len();
149 let lengths = lengths.cast(DType::Primitive(PType::U64, nullability))?;
150
151 if matches!(nullability, Nullability::Nullable) {
153 lengths.mask(validity.to_array(len))
154 } else {
155 Ok(lengths)
156 }
157}
158
159fn list_length_from_offsets(list: ArrayView<'_, List>) -> VortexResult<ArrayRef> {
162 let offsets = list.offsets();
163 let n = offsets.len().saturating_sub(1);
164
165 offsets
166 .slice(1..offsets.len())?
167 .binary(offsets.slice(0..n)?, Operator::Sub)
168}
169
170struct AnyList;
172
173impl Matcher for AnyList {
174 type Match<'a> = ();
175
176 fn try_match(array: &ArrayRef) -> Option<Self::Match<'_>> {
177 (array.as_opt::<List>().is_some()
178 || array.as_opt::<ListView>().is_some()
179 || array.as_opt::<FixedSizeList>().is_some())
180 .then_some(())
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use std::sync::Arc;
187
188 use rstest::rstest;
189 use vortex_buffer::buffer;
190 use vortex_error::VortexResult;
191
192 use crate::ArrayRef;
193 use crate::IntoArray;
194 use crate::VortexSessionExecute;
195 use crate::array_session;
196 use crate::arrays::BoolArray;
197 use crate::arrays::ConstantArray;
198 use crate::arrays::FixedSizeListArray;
199 use crate::arrays::ListArray;
200 use crate::arrays::ListViewArray;
201 use crate::arrays::PrimitiveArray;
202 use crate::assert_arrays_eq;
203 use crate::dtype::DType;
204 use crate::dtype::Nullability;
205 use crate::dtype::PType;
206 use crate::expr::cast;
207 use crate::expr::list_length;
208 use crate::expr::root;
209 use crate::scalar::Scalar;
210 use crate::validity::Validity;
211
212 fn create_list_elements() -> ArrayRef {
213 PrimitiveArray::from_option_iter::<i32, _>([
214 Some(1),
215 Some(2),
216 Some(3),
217 Some(4),
218 Some(5),
219 Some(6),
220 None,
221 ])
222 .into_array()
223 }
224
225 #[rstest]
226 #[case(buffer![0u32, 2, 5, 5, 7].into_array())]
227 #[case(buffer![0u64, 2, 5, 5, 7].into_array())]
228 fn test_list_length(#[case] offsets: ArrayRef) -> VortexResult<()> {
229 let elements = create_list_elements();
230 let list = ListArray::try_new(elements, offsets, Validity::NonNullable)?.into_array();
231 let result = list.apply(&list_length(root()))?;
232 let mut ctx = array_session().create_execution_ctx();
233 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 3, 0, 2]), &mut ctx);
234 Ok(())
235 }
236
237 #[rstest]
238 #[case(buffer![0u32, 2, 5, 5, 7].into_array())]
239 #[case(buffer![0u64, 2, 5, 5, 7].into_array())]
240 fn test_nullable_list_length(#[case] offsets: ArrayRef) -> VortexResult<()> {
241 let elements = create_list_elements();
242 let list = ListArray::try_new(
243 elements,
244 offsets,
245 Validity::Array(BoolArray::from_iter([true, false, true, false]).into_array()),
246 )?
247 .into_array();
248 let result = list.apply(&list_length(root()))?;
249
250 let mut ctx = array_session().create_execution_ctx();
251
252 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(0), None]);
253
254 assert_arrays_eq!(result, expected, &mut ctx);
255
256 Ok(())
257 }
258
259 #[test]
260 fn test_null_scalar_list_length() -> VortexResult<()> {
261 let null_scalar = Scalar::null(DType::List(
262 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
263 Nullability::Nullable,
264 ));
265 let array = ConstantArray::new(null_scalar, 2).into_array();
266 let result = array.apply(&list_length(root()))?;
267
268 let mut ctx = array_session().create_execution_ctx();
269 assert!(!result.is_valid(0, &mut ctx)?);
270 assert!(!result.is_valid(1, &mut ctx)?);
271 assert_arrays_eq!(
272 result,
273 PrimitiveArray::from_option_iter([None::<u64>, None]),
274 &mut ctx
275 );
276 Ok(())
277 }
278
279 #[test]
280 fn test_listview_length() -> VortexResult<()> {
281 let elements = create_list_elements();
282 let lv = ListViewArray::new(
283 elements,
284 buffer![5u32, 0, 4, 1].into_array(),
285 buffer![2u32, 3, 0, 2].into_array(),
286 Validity::NonNullable,
287 )
288 .into_array();
289 let result = lv.apply(&list_length(root()))?;
290 let mut ctx = array_session().create_execution_ctx();
291 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 3, 0, 2]), &mut ctx);
292 Ok(())
293 }
294
295 #[test]
296 fn test_listview_length_nullable() -> VortexResult<()> {
297 let elements = create_list_elements();
298 let lv = ListViewArray::new(
299 elements,
300 buffer![5u32, 0, 4, 1].into_array(),
301 buffer![2u32, 3, 0, 2].into_array(),
302 Validity::Array(BoolArray::from_iter([true, false, true, false]).into_array()),
303 )
304 .into_array();
305 let result = lv.apply(&list_length(root()))?;
306
307 let mut ctx = array_session().create_execution_ctx();
308
309 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(0), None]);
310 assert_arrays_eq!(result, expected, &mut ctx);
311 Ok(())
312 }
313
314 #[test]
315 fn test_list_length_take() -> VortexResult<()> {
316 let elements = create_list_elements();
317 let list = ListArray::try_new(
318 elements,
319 buffer![0u32, 2, 5, 5, 7].into_array(),
320 Validity::NonNullable,
321 )?
322 .into_array();
323 let taken = list.take(buffer![3u64, 0, 2].into_array())?;
324
325 let result = taken.apply(&list_length(root()))?;
326 let mut ctx = array_session().create_execution_ctx();
327 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 2, 0]), &mut ctx);
328 Ok(())
329 }
330
331 fn create_fixed_size_list(validity: Validity) -> ArrayRef {
332 let elements = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5, 6, 7, 8]).into_array();
334 FixedSizeListArray::new(elements, 2, validity, 4).into_array()
335 }
336
337 #[test]
338 fn test_fixed_size_list_length() -> VortexResult<()> {
339 let fsl = create_fixed_size_list(Validity::NonNullable);
340 let result = fsl.apply(&list_length(root()))?;
341
342 let mut ctx = array_session().create_execution_ctx();
343 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 2, 2, 2]), &mut ctx);
344 Ok(())
345 }
346
347 #[test]
348 fn test_fixed_size_list_length_nullable() -> VortexResult<()> {
349 let fsl = create_fixed_size_list(Validity::Array(
350 BoolArray::from_iter([true, false, true, false]).into_array(),
351 ));
352 let result = fsl.apply(&list_length(root()))?;
353
354 let mut ctx = array_session().create_execution_ctx();
355
356 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(2), None]);
357 assert_arrays_eq!(result, expected, &mut ctx);
358 Ok(())
359 }
360
361 #[test]
362 fn test_fallible_child_expression_fails() -> VortexResult<()> {
363 let fsl = create_fixed_size_list(Validity::Array(
364 BoolArray::from_iter([true, false, true, false]).into_array(),
365 ));
366 let failing_cast_dtype = DType::FixedSizeList(
367 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
368 2,
369 Nullability::NonNullable,
370 );
371
372 let lengths = fsl.apply(&list_length(cast(root(), failing_cast_dtype)))?;
373
374 let mut ctx = array_session().create_execution_ctx();
375 let result = lengths.execute::<ArrayRef>(&mut ctx);
376
377 assert!(result.is_err());
378
379 let err_message = result.unwrap_err().to_string();
380
381 assert!(
382 err_message.contains("Cannot cast array with invalid values to non-nullable type.")
383 );
384
385 Ok(())
386 }
387
388 #[test]
389 fn test_display() {
390 let expr = list_length(root());
391 assert_eq!(expr.to_string(), "vortex.list.length($)");
392 }
393}