Skip to main content

vortex_array/scalar_fn/fns/
list_length.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use 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/// Number of elements in each list of a `List` or `FixedSizeList` typed array.
38///
39/// This is computed purely from the list's offsets (`ListArray`), sizes (`ListViewArray`), or
40/// dtype (`FixedSizeListArray`) without reading the element *values*. Validity is carried over
41/// from the original array.
42#[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        // A null list has a null length, and the length of a valid list is a non-null value
104        // determined by that list alone, with `return_dtype` carrying over the input nullability.
105        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        // The length of fixed-size list is constant, so just need to carry over validity
131        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        // Length array is exactly the sizes child
138        (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    // Cast to `U64`
148    let len = lengths.len();
149    let lengths = lengths.cast(DType::Primitive(PType::U64, nullability))?;
150
151    // Carry over validity mask for nullable arrays
152    if matches!(nullability, Nullability::Nullable) {
153        lengths.mask(validity.to_array(len))
154    } else {
155        Ok(lengths)
156    }
157}
158
159/// Calculate the lengths of `ListArray` elements via the `offsets` child:
160/// `length[i] = offsets[i + 1] - offsets[i]`.
161fn 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
170/// Matches an `Array<List>`, `Array<ListView>`, or `Array<FixedSizeList>`
171struct 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        // 4 lists of size 2 over 8 primitive elements.
343        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}