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 mut ctx = array_session().create_execution_ctx();
230 let elements = create_list_elements();
231 let list =
232 ListArray::try_new(elements, offsets, Validity::NonNullable, &mut ctx)?.into_array();
233 let result = list.apply(&list_length(root()))?;
234 let mut ctx = array_session().create_execution_ctx();
235 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 3, 0, 2]), &mut ctx);
236 Ok(())
237 }
238
239 #[rstest]
240 #[case(buffer![0u32, 2, 5, 5, 7].into_array())]
241 #[case(buffer![0u64, 2, 5, 5, 7].into_array())]
242 fn test_nullable_list_length(#[case] offsets: ArrayRef) -> VortexResult<()> {
243 let mut ctx = array_session().create_execution_ctx();
244 let elements = create_list_elements();
245 let list = ListArray::try_new(
246 elements,
247 offsets,
248 Validity::Array(BoolArray::from_iter([true, false, true, false]).into_array()),
249 &mut ctx,
250 )?
251 .into_array();
252 let result = list.apply(&list_length(root()))?;
253
254 let mut ctx = array_session().create_execution_ctx();
255
256 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(0), None]);
257
258 assert_arrays_eq!(result, expected, &mut ctx);
259
260 Ok(())
261 }
262
263 #[test]
264 fn test_null_scalar_list_length() -> VortexResult<()> {
265 let null_scalar = Scalar::null(DType::List(
266 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
267 Nullability::Nullable,
268 ));
269 let array = ConstantArray::new(null_scalar, 2).into_array();
270 let result = array.apply(&list_length(root()))?;
271
272 let mut ctx = array_session().create_execution_ctx();
273 assert!(!result.is_valid(0, &mut ctx)?);
274 assert!(!result.is_valid(1, &mut ctx)?);
275 assert_arrays_eq!(
276 result,
277 PrimitiveArray::from_option_iter([None::<u64>, None]),
278 &mut ctx
279 );
280 Ok(())
281 }
282
283 #[test]
284 fn test_listview_length() -> VortexResult<()> {
285 let mut ctx = array_session().create_execution_ctx();
286 let elements = create_list_elements();
287 let lv = ListViewArray::new(
288 elements,
289 buffer![5u32, 0, 4, 1].into_array(),
290 buffer![2u32, 3, 0, 2].into_array(),
291 Validity::NonNullable,
292 &mut ctx,
293 )
294 .into_array();
295 let result = lv.apply(&list_length(root()))?;
296 let mut ctx = array_session().create_execution_ctx();
297 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 3, 0, 2]), &mut ctx);
298 Ok(())
299 }
300
301 #[test]
302 fn test_listview_length_nullable() -> VortexResult<()> {
303 let mut ctx = array_session().create_execution_ctx();
304 let elements = create_list_elements();
305 let lv = ListViewArray::new(
306 elements,
307 buffer![5u32, 0, 4, 1].into_array(),
308 buffer![2u32, 3, 0, 2].into_array(),
309 Validity::Array(BoolArray::from_iter([true, false, true, false]).into_array()),
310 &mut ctx,
311 )
312 .into_array();
313 let result = lv.apply(&list_length(root()))?;
314
315 let mut ctx = array_session().create_execution_ctx();
316
317 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(0), None]);
318 assert_arrays_eq!(result, expected, &mut ctx);
319 Ok(())
320 }
321
322 #[test]
323 fn test_list_length_take() -> VortexResult<()> {
324 let mut ctx = array_session().create_execution_ctx();
325 let elements = create_list_elements();
326 let list = ListArray::try_new(
327 elements,
328 buffer![0u32, 2, 5, 5, 7].into_array(),
329 Validity::NonNullable,
330 &mut ctx,
331 )?
332 .into_array();
333 let taken = list.take(buffer![3u64, 0, 2].into_array())?;
334
335 let result = taken.apply(&list_length(root()))?;
336 let mut ctx = array_session().create_execution_ctx();
337 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 2, 0]), &mut ctx);
338 Ok(())
339 }
340
341 fn create_fixed_size_list(validity: Validity) -> ArrayRef {
342 let elements = PrimitiveArray::from_iter([1i32, 2, 3, 4, 5, 6, 7, 8]).into_array();
344 FixedSizeListArray::new(elements, 2, validity, 4).into_array()
345 }
346
347 #[test]
348 fn test_fixed_size_list_length() -> VortexResult<()> {
349 let fsl = create_fixed_size_list(Validity::NonNullable);
350 let result = fsl.apply(&list_length(root()))?;
351
352 let mut ctx = array_session().create_execution_ctx();
353 assert_arrays_eq!(result, PrimitiveArray::from_iter([2u64, 2, 2, 2]), &mut ctx);
354 Ok(())
355 }
356
357 #[test]
358 fn test_fixed_size_list_length_nullable() -> VortexResult<()> {
359 let fsl = create_fixed_size_list(Validity::Array(
360 BoolArray::from_iter([true, false, true, false]).into_array(),
361 ));
362 let result = fsl.apply(&list_length(root()))?;
363
364 let mut ctx = array_session().create_execution_ctx();
365
366 let expected = PrimitiveArray::from_option_iter::<u64, _>([Some(2), None, Some(2), None]);
367 assert_arrays_eq!(result, expected, &mut ctx);
368 Ok(())
369 }
370
371 #[test]
372 fn test_fallible_child_expression_fails() -> VortexResult<()> {
373 let fsl = create_fixed_size_list(Validity::Array(
374 BoolArray::from_iter([true, false, true, false]).into_array(),
375 ));
376 let failing_cast_dtype = DType::FixedSizeList(
377 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
378 2,
379 Nullability::NonNullable,
380 );
381
382 let lengths = fsl.apply(&list_length(cast(root(), failing_cast_dtype)))?;
383
384 let mut ctx = array_session().create_execution_ctx();
385 let result = lengths.execute::<ArrayRef>(&mut ctx);
386
387 assert!(result.is_err());
388
389 let err_message = result.unwrap_err().to_string();
390
391 assert!(
392 err_message.contains("Cannot cast array with invalid values to non-nullable type.")
393 );
394
395 Ok(())
396 }
397
398 #[test]
399 fn test_display() {
400 let expr = list_length(root());
401 assert_eq!(expr.to_string(), "vortex.list.length($)");
402 }
403}