Skip to main content

vortex_array/builders/
list.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::sync::Arc;
6
7use num_traits::AsPrimitive;
8use vortex_buffer::BufferAllocatorRef;
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_bail;
12use vortex_error::vortex_ensure;
13use vortex_error::vortex_panic;
14
15use crate::ArrayRef;
16use crate::Canonical;
17use crate::ExecutionCtx;
18use crate::IntoArray;
19use crate::array::ArrayView;
20use crate::arrays::List;
21use crate::arrays::ListArray;
22use crate::arrays::ListView;
23use crate::arrays::ListViewArray;
24use crate::arrays::PrimitiveArray;
25use crate::arrays::list::ListArraySlotsExt;
26use crate::arrays::listview::ListViewArraySlotsExt;
27use crate::arrays::listview::ListViewRebuildMode;
28use crate::builders::ArrayBuilder;
29use crate::builders::ChildBuilder;
30use crate::builders::DEFAULT_BUILDER_CAPACITY;
31use crate::builders::PrimitiveBuilder;
32use crate::builders::ValidityBuilder;
33use crate::dtype::DType;
34use crate::dtype::IntegerPType;
35use crate::dtype::Nullability;
36use crate::dtype::Nullability::NonNullable;
37use crate::dtype::OffsetBuilderPType;
38use crate::match_each_integer_ptype;
39use crate::scalar::ListScalar;
40use crate::scalar::Scalar;
41
42/// The builder for building a [`ListArray`], parametrized by the [`OffsetBuilderPType`] of the
43/// `offsets` builder.
44pub struct ListBuilder<O: OffsetBuilderPType> {
45    /// The [`DType`] of the [`ListArray`]. This **must** be a [`DType::List`].
46    dtype: DType,
47
48    /// The builder for the underlying elements of the [`ListArray`].
49    elements_builder: ChildBuilder,
50
51    /// The builder for the `offsets` into the `elements` array.
52    offsets_builder: PrimitiveBuilder<O>,
53
54    /// The null map builder of the [`ListArray`].
55    nulls: ValidityBuilder,
56}
57
58impl<O: OffsetBuilderPType> ListBuilder<O> {
59    /// Creates a new `ListBuilder` with a capacity of [`DEFAULT_BUILDER_CAPACITY`].
60    #[deprecated(note = "use `new_in` with an explicit allocator")]
61    pub fn new(value_dtype: Arc<DType>, nullability: Nullability) -> Self {
62        Self::new_in(value_dtype, nullability, BufferAllocatorRef::static_ref())
63    }
64
65    /// Creates a new `ListBuilder` with the default capacity using `allocator`.
66    pub fn new_in(
67        value_dtype: Arc<DType>,
68        nullability: Nullability,
69        allocator: &BufferAllocatorRef,
70    ) -> Self {
71        Self::with_capacity_in(
72            value_dtype,
73            nullability,
74            // We arbitrarily choose 2 times the number of list scalars for the capacity of the
75            // elements builder since we cannot know this ahead of time.
76            DEFAULT_BUILDER_CAPACITY * 2,
77            DEFAULT_BUILDER_CAPACITY,
78            allocator,
79        )
80    }
81
82    /// Create a new [`ListArray`] builder with a with the given `capacity`, as well as an initial
83    /// capacity for the `elements` builder (since we cannot know that ahead of time solely based on
84    /// the outer array `capacity`).
85    ///
86    /// # Notes
87    ///
88    /// The number of offsets is one more than the length (# of list scalars) in the array.
89    #[deprecated(note = "use `with_capacity_in` with an explicit allocator")]
90    pub fn with_capacity(
91        value_dtype: Arc<DType>,
92        nullability: Nullability,
93        elements_capacity: usize,
94        capacity: usize,
95    ) -> Self {
96        Self::with_capacity_in(
97            value_dtype,
98            nullability,
99            elements_capacity,
100            capacity,
101            BufferAllocatorRef::static_ref(),
102        )
103    }
104
105    /// Creates a list builder with the given capacities using `allocator`.
106    pub fn with_capacity_in(
107        value_dtype: Arc<DType>,
108        nullability: Nullability,
109        elements_capacity: usize,
110        capacity: usize,
111        allocator: &BufferAllocatorRef,
112    ) -> Self {
113        let elements_builder =
114            ChildBuilder::with_capacity(value_dtype.as_ref(), elements_capacity, allocator);
115        let mut offsets_builder =
116            PrimitiveBuilder::<O>::with_capacity_in(NonNullable, capacity + 1, allocator);
117
118        // The first offset is always 0 and represents an empty list.
119        offsets_builder.append_zero();
120
121        Self {
122            elements_builder,
123            offsets_builder,
124            nulls: ValidityBuilder::new(capacity, allocator),
125            dtype: DType::List(value_dtype, nullability),
126        }
127    }
128
129    /// Appends an array as a single non-null list entry to the builder.
130    ///
131    /// The input `array` must have the same dtype as the element dtype of this list builder.
132    ///
133    /// Note that the list entry will be non-null but the elements themselves are allowed to be null
134    /// (only if the elements [`DType`] in nullable, of course).
135    pub fn append_array_as_list(
136        &mut self,
137        array: &ArrayRef,
138        ctx: &mut ExecutionCtx,
139    ) -> VortexResult<()> {
140        vortex_ensure!(
141            array.dtype() == self.element_dtype(),
142            "Array dtype {:?} does not match list element dtype {:?}",
143            array.dtype(),
144            self.element_dtype()
145        );
146
147        self.elements_builder.append_array(array, ctx)?;
148        self.nulls.append_non_null();
149        self.offsets_builder.append_value(
150            O::from_usize(self.elements_builder.len())
151                .vortex_expect("Failed to convert from usize to O"),
152        );
153
154        Ok(())
155    }
156
157    /// Appends a list `value` to the builder.
158    pub fn append_value(&mut self, value: ListScalar) -> VortexResult<()> {
159        match value.elements() {
160            None => {
161                if self.dtype.nullability() == NonNullable {
162                    vortex_bail!("Cannot append null value to non-nullable list");
163                }
164                self.append_null();
165            }
166            Some(elements) => {
167                for scalar in elements {
168                    // TODO(connor): This is slow, we should be able to append multiple values at
169                    // once, or the list scalar should hold an Array
170                    self.elements_builder.append_scalar(&scalar)?;
171                }
172
173                self.nulls.append_non_null();
174                self.offsets_builder.append_value(
175                    O::from_usize(self.elements_builder.len())
176                        .vortex_expect("Failed to convert from usize to O"),
177                );
178            }
179        }
180
181        Ok(())
182    }
183
184    /// Finishes the builder directly into a [`ListArray`].
185    pub fn finish_into_list(&mut self) -> ListArray {
186        assert_eq!(
187            self.offsets_builder.len(),
188            self.nulls.len() + 1,
189            "offsets length must be one more than nulls length."
190        );
191
192        ListArray::try_new(
193            self.elements_builder.finish(),
194            self.offsets_builder.finish(),
195            self.nulls.finish_with_nullability(self.dtype.nullability()),
196        )
197        .vortex_expect("Buffer, offsets, and validity must have same length.")
198    }
199
200    /// The [`DType`] of the inner elements. Note that this is **not** the same as the [`DType`] of
201    /// the outer `List`.
202    pub fn element_dtype(&self) -> &DType {
203        let DType::List(element_dtype, _) = &self.dtype else {
204            vortex_panic!("`ListBuilder` has an incorrect dtype: {}", self.dtype);
205        };
206
207        element_dtype
208    }
209
210    /// Appends the values of a [`List`]-encoded `array` to this builder.
211    ///
212    /// List encodings dispatch here through
213    /// [`match_each_list_builder!`](crate::match_each_list_builder) because the concrete list
214    /// builders are generic over their offset integer type, which cannot be named through a
215    /// `dyn ArrayBuilder`.
216    pub fn append_list_array(
217        &mut self,
218        array: ArrayView<'_, List>,
219        ctx: &mut ExecutionCtx,
220    ) -> VortexResult<()> {
221        if array.is_empty() {
222            return Ok(());
223        }
224
225        self.nulls.append_validity(array.validity()?, array.len());
226
227        let num_lists = array.len();
228        let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
229        match_each_integer_ptype!(offsets.ptype(), |OffsetType| {
230            let offsets = offsets.as_slice::<OffsetType>();
231            let first: usize = offsets[0].as_();
232            let last: usize = offsets[num_lists].as_();
233
234            // Lists in a `ListArray` are contiguous, so the referenced elements can be appended
235            // in bulk and the offsets rebased onto this builder's elements.
236            let elements_base = self.elements_builder.len();
237            if last > first {
238                self.elements_builder
239                    .append_array(&array.elements().slice(first..last)?, ctx)?;
240            }
241
242            self.offsets_builder.reserve_exact(num_lists);
243            let mut offsets_range = self.offsets_builder.uninit_range(num_lists);
244            for i in 0..num_lists {
245                let end: usize = offsets[i + 1].as_();
246                offsets_range.set_value(
247                    i,
248                    O::from_usize(end - first + elements_base)
249                        .vortex_expect("Failed to convert offset"),
250                );
251            }
252            // SAFETY: We have initialized all `num_lists` values, and since the `offsets` array is
253            // non-nullable, we are done.
254            unsafe { offsets_range.finish() };
255        });
256        Ok(())
257    }
258
259    /// Appends the values of a [`ListView`]-encoded `array` to this builder.
260    ///
261    /// See [`append_list_array`](Self::append_list_array); this is the same hook for the canonical
262    /// [`ListViewArray`] encoding.
263    ///
264    /// A `ListArray`'s offsets can only describe contiguous, in-order lists, so views laid out any
265    /// other way (overlapping, out of order, or with interior gaps) are flattened first.
266    pub fn append_listview_array(
267        &mut self,
268        array: ArrayView<'_, ListView>,
269        ctx: &mut ExecutionCtx,
270    ) -> VortexResult<()> {
271        if array.is_empty() {
272            return Ok(());
273        }
274
275        self.nulls.append_validity(array.validity()?, array.len());
276
277        // Flatten the views into the only layout `ListArray` offsets can express. This is a cheap
278        // clone when they already are laid out that way, and the flattened result keeps the
279        // original validity, so the null map appended above still describes it.
280        let array = array
281            .into_owned()
282            .rebuild(ListViewRebuildMode::MakeZeroCopyToList, ctx)?;
283        debug_assert!(array.is_zero_copy_to_list());
284
285        // Note that `ListViewArray` has `n` offsets and sizes, not `n+1` offsets like `ListArray`.
286        let elements = array.elements();
287        let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
288        let sizes = array.sizes().clone().execute::<PrimitiveArray>(ctx)?;
289
290        match_each_integer_ptype!(offsets.ptype(), |OffsetType| {
291            match_each_integer_ptype!(sizes.ptype(), |SizeType| {
292                extend_from_listview(
293                    self,
294                    elements,
295                    offsets.as_slice::<OffsetType>(),
296                    sizes.as_slice::<SizeType>(),
297                    ctx,
298                )?
299            })
300        });
301        Ok(())
302    }
303}
304
305/// Appends the lists of a zero-copy-to-list [`ListViewArray`] (`n` offsets and sizes) into a
306/// [`ListBuilder`], converting into the `ListArray` (`n + 1` offsets) layout.
307///
308/// The caller must have made `new_offsets` and `new_sizes` zero-copyable to a `ListArray`, so the
309/// lists they describe are contiguous and in order — which is the only layout `ListArray` offsets
310/// can express. That lets the referenced elements be appended in bulk, with the offsets rebased
311/// onto this builder's elements, instead of appending a slice per list.
312fn extend_from_listview<O, OffsetType, SizeType>(
313    builder: &mut ListBuilder<O>,
314    new_elements: &ArrayRef,
315    new_offsets: &[OffsetType],
316    new_sizes: &[SizeType],
317    ctx: &mut ExecutionCtx,
318) -> VortexResult<()>
319where
320    O: OffsetBuilderPType,
321    OffsetType: IntegerPType,
322    SizeType: IntegerPType,
323{
324    let num_lists = new_offsets.len();
325    debug_assert_eq!(num_lists, new_sizes.len());
326
327    // Leading and trailing unreferenced elements are allowed even in a zero-copy-to-list layout,
328    // so the referenced range is bounded by the first list's start and the last list's end.
329    let first: usize = new_offsets[0].as_();
330    let last: usize = new_offsets[num_lists - 1].as_() + new_sizes[num_lists - 1].as_();
331
332    let elements_base = builder.elements_builder.len();
333    if last > first {
334        builder
335            .elements_builder
336            .append_array(&new_elements.slice(first..last)?, ctx)?;
337    }
338
339    builder.offsets_builder.reserve_exact(num_lists);
340    let mut offsets_range = builder.offsets_builder.uninit_range(num_lists);
341    for i in 0..num_lists {
342        let end: usize = new_offsets[i].as_() + new_sizes[i].as_();
343        offsets_range.set_value(
344            i,
345            O::from_usize(end - first + elements_base).vortex_expect("Failed to convert offset"),
346        );
347    }
348
349    // SAFETY: We have initialized all `num_lists` values, and since the `offsets` array is
350    // non-nullable, we are done.
351    unsafe { offsets_range.finish() };
352    Ok(())
353}
354
355impl<O: OffsetBuilderPType> ArrayBuilder for ListBuilder<O> {
356    fn as_any(&self) -> &dyn Any {
357        self
358    }
359
360    fn as_any_mut(&mut self) -> &mut dyn Any {
361        self
362    }
363
364    fn dtype(&self) -> &DType {
365        &self.dtype
366    }
367
368    fn len(&self) -> usize {
369        self.nulls.len()
370    }
371
372    fn append_zeros(&mut self, n: usize) {
373        let curr_len = self.elements_builder.len();
374        for _ in 0..n {
375            self.offsets_builder.append_value(
376                O::from_usize(curr_len).vortex_expect("Failed to convert from usize to <O>"),
377            )
378        }
379        self.nulls.append_n_non_nulls(n);
380    }
381
382    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
383        let curr_len = self.elements_builder.len();
384        for _ in 0..n {
385            // A list with a null element is can be a list with a zero-span offset and a validity
386            // bit set
387            self.offsets_builder.append_value(
388                O::from_usize(curr_len).vortex_expect("Failed to convert from usize to <O>"),
389            )
390        }
391        self.nulls.append_n_nulls(n);
392    }
393
394    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
395        vortex_ensure!(
396            scalar.dtype() == self.dtype(),
397            "ListBuilder expected scalar with dtype {}, got {}",
398            self.dtype(),
399            scalar.dtype()
400        );
401
402        self.append_value(scalar.as_list())
403    }
404
405    fn reserve_exact(&mut self, additional: usize) {
406        self.elements_builder.reserve_exact(additional);
407        self.offsets_builder.reserve_exact(additional);
408        self.nulls.reserve_exact(additional);
409    }
410
411    fn finish(&mut self) -> ArrayRef {
412        self.finish_into_list().into_array()
413    }
414
415    fn finish_into_canonical(&mut self, ctx: &mut ExecutionCtx) -> Canonical {
416        let listview = self
417            .finish()
418            .execute::<ListViewArray>(ctx)
419            .vortex_expect("list builder should canonicalize to listview");
420        Canonical::List(listview)
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use std::sync::Arc;
427
428    use Nullability::NonNullable;
429    use Nullability::Nullable;
430    use vortex_buffer::BufferAllocatorRef;
431    use vortex_buffer::buffer;
432    use vortex_error::VortexExpect;
433    use vortex_error::VortexResult;
434
435    use crate::IntoArray;
436    use crate::array_session;
437    use crate::arrays::ChunkedArray;
438    use crate::arrays::ListViewArray;
439    use crate::arrays::PrimitiveArray;
440    use crate::arrays::list::ListArraySlotsExt;
441    use crate::arrays::listview::ListViewArrayExt;
442    use crate::arrays::listview::ListViewArraySlotsExt;
443    use crate::assert_arrays_eq;
444    use crate::builders::ArrayBuilder;
445    use crate::builders::ListViewBuilder;
446    use crate::builders::builder_with_capacity_in;
447    use crate::builders::list::ListArray;
448    use crate::builders::list::ListBuilder;
449    use crate::dtype::DType;
450    use crate::dtype::Nullability;
451    use crate::dtype::OffsetBuilderPType;
452    use crate::dtype::PType::I32;
453    use crate::executor::VortexSessionExecute;
454    use crate::scalar::Scalar;
455    use crate::validity::Validity;
456
457    #[test]
458    fn test_empty() {
459        let mut builder = ListBuilder::<u32>::with_capacity_in(
460            Arc::new(I32.into()),
461            NonNullable,
462            0,
463            0,
464            BufferAllocatorRef::static_ref(),
465        );
466
467        let list = builder.finish();
468        assert_eq!(list.len(), 0);
469    }
470
471    #[test]
472    fn test_values() {
473        let dtype: Arc<DType> = Arc::new(I32.into());
474        let mut builder = ListBuilder::<u32>::with_capacity_in(
475            Arc::clone(&dtype),
476            NonNullable,
477            0,
478            0,
479            BufferAllocatorRef::static_ref(),
480        );
481
482        builder
483            .append_value(
484                Scalar::list(
485                    Arc::clone(&dtype),
486                    vec![1i32.into(), 2i32.into(), 3i32.into()],
487                    NonNullable,
488                )
489                .as_list(),
490            )
491            .unwrap();
492
493        builder
494            .append_value(
495                Scalar::list(
496                    dtype,
497                    vec![4i32.into(), 5i32.into(), 6i32.into()],
498                    NonNullable,
499                )
500                .as_list(),
501            )
502            .unwrap();
503
504        let list = builder.finish();
505        assert_eq!(list.len(), 2);
506
507        let mut ctx = array_session().create_execution_ctx();
508        let list_array = list.execute::<ListViewArray>(&mut ctx).unwrap();
509
510        assert_eq!(list_array.list_elements_at(0).unwrap().len(), 3);
511        assert_eq!(list_array.list_elements_at(1).unwrap().len(), 3);
512    }
513
514    #[test]
515    fn test_append_empty_list() {
516        let dtype: Arc<DType> = Arc::new(I32.into());
517        let mut builder = ListBuilder::<u32>::with_capacity_in(
518            Arc::clone(&dtype),
519            NonNullable,
520            0,
521            0,
522            BufferAllocatorRef::static_ref(),
523        );
524
525        assert!(
526            builder
527                .append_value(Scalar::list_empty(dtype, NonNullable).as_list())
528                .is_ok()
529        )
530    }
531
532    #[test]
533    fn test_nullable_values() {
534        let dtype: Arc<DType> = Arc::new(I32.into());
535        let mut builder = ListBuilder::<u32>::with_capacity_in(
536            Arc::clone(&dtype),
537            Nullable,
538            0,
539            0,
540            BufferAllocatorRef::static_ref(),
541        );
542
543        builder
544            .append_value(
545                Scalar::list(
546                    Arc::clone(&dtype),
547                    vec![1i32.into(), 2i32.into(), 3i32.into()],
548                    NonNullable,
549                )
550                .as_list(),
551            )
552            .unwrap();
553
554        builder
555            .append_value(Scalar::list_empty(Arc::clone(&dtype), NonNullable).as_list())
556            .unwrap();
557
558        builder
559            .append_value(
560                Scalar::list(
561                    dtype,
562                    vec![4i32.into(), 5i32.into(), 6i32.into()],
563                    NonNullable,
564                )
565                .as_list(),
566            )
567            .unwrap();
568
569        let list = builder.finish();
570        assert_eq!(list.len(), 3);
571
572        let mut ctx = array_session().create_execution_ctx();
573        let list_array = list.execute::<ListViewArray>(&mut ctx).unwrap();
574
575        assert_eq!(list_array.list_elements_at(0).unwrap().len(), 3);
576        assert_eq!(list_array.list_elements_at(1).unwrap().len(), 0);
577        assert_eq!(list_array.list_elements_at(2).unwrap().len(), 3);
578    }
579
580    fn test_extend_builder_gen<O: OffsetBuilderPType>() {
581        let list = ListArray::from_iter_opt_slow::<O, _, _>(
582            [Some(vec![0, 1, 2]), None, Some(vec![4, 5])],
583            Arc::new(I32.into()),
584        )
585        .unwrap()
586        .into_array();
587        assert_eq!(list.len(), 3);
588
589        let mut ctx = array_session().create_execution_ctx();
590
591        let mut builder = ListBuilder::<O>::with_capacity_in(
592            Arc::new(I32.into()),
593            Nullable,
594            18,
595            9,
596            BufferAllocatorRef::static_ref(),
597        );
598        list.append_to_builder(&mut builder, &mut ctx).unwrap();
599        list.append_to_builder(&mut builder, &mut ctx).unwrap();
600        list.slice(0..0)
601            .unwrap()
602            .append_to_builder(&mut builder, &mut ctx)
603            .unwrap();
604        list.slice(1..3)
605            .unwrap()
606            .append_to_builder(&mut builder, &mut ctx)
607            .unwrap();
608
609        let expected = ListArray::from_iter_opt_slow::<O, _, _>(
610            [
611                Some(vec![0, 1, 2]),
612                None,
613                Some(vec![4, 5]),
614                Some(vec![0, 1, 2]),
615                None,
616                Some(vec![4, 5]),
617                None,
618                Some(vec![4, 5]),
619            ],
620            Arc::new(DType::Primitive(I32, NonNullable)),
621        )
622        .unwrap()
623        .into_array()
624        .execute::<ListViewArray>(&mut ctx)
625        .unwrap();
626
627        let actual = builder.finish_into_canonical(&mut ctx).into_listview();
628
629        assert_arrays_eq!(actual.elements(), expected.elements(), &mut ctx);
630
631        assert_arrays_eq!(actual.offsets(), expected.offsets(), &mut ctx);
632
633        assert!(
634            actual
635                .validity()
636                .vortex_expect("list validity should be derivable")
637                .mask_eq(
638                    &expected
639                        .validity()
640                        .vortex_expect("list validity should be derivable"),
641                    actual.len(),
642                    &mut ctx,
643                )
644                .unwrap(),
645        );
646    }
647
648    /// `append_to_builder` must handle any list builder kind without assuming the offset/size
649    /// integer types produced by `builder_with_capacity`. It appends a `List`-encoded array and a
650    /// `ListView`-encoded array into `ListViewBuilder`s and `ListBuilder`s with assorted (and
651    /// non-`u64`) offset/size types.
652    #[test]
653    fn test_append_to_builder_any_list_builder() -> VortexResult<()> {
654        let mut ctx = array_session().create_execution_ctx();
655
656        let list = ListArray::from_iter_opt_slow::<u64, _, _>(
657            [Some(vec![0, 1, 2]), None, Some(vec![4, 5])],
658            Arc::new(I32.into()),
659        )?
660        .into_array();
661        let listview = list
662            .clone()
663            .execute::<ListViewArray>(&mut ctx)?
664            .into_array();
665        let elem_dtype = || Arc::new(I32.into());
666
667        // `builder_with_capacity` produces a `ListViewBuilder` for `DType::List`; appending the
668        // `List`-encoded array must dispatch into it instead of bailing.
669        let mut listview_builder =
670            builder_with_capacity_in(list.dtype(), list.len(), BufferAllocatorRef::static_ref());
671        list.append_to_builder(listview_builder.as_mut(), &mut ctx)?;
672        assert_arrays_eq!(listview_builder.finish(), list, &mut ctx);
673
674        // A `ListViewBuilder` with non-`u64` (including signed) offset and size types must work
675        // for both source encodings.
676        let mut lv_u64_u32 = ListViewBuilder::<u64, u32>::with_capacity_in(
677            elem_dtype(),
678            Nullable,
679            8,
680            4,
681            BufferAllocatorRef::static_ref(),
682        );
683        list.append_to_builder(&mut lv_u64_u32, &mut ctx)?;
684        assert_arrays_eq!(lv_u64_u32.finish(), list, &mut ctx);
685
686        let mut lv_i64_i32 = ListViewBuilder::<i64, i32>::with_capacity_in(
687            elem_dtype(),
688            Nullable,
689            8,
690            4,
691            BufferAllocatorRef::static_ref(),
692        );
693        list.append_to_builder(&mut lv_i64_i32, &mut ctx)?;
694        assert_arrays_eq!(lv_i64_i32.finish(), list, &mut ctx);
695
696        let mut lv_u32_u32 = ListViewBuilder::<u32, u32>::with_capacity_in(
697            elem_dtype(),
698            Nullable,
699            8,
700            4,
701            BufferAllocatorRef::static_ref(),
702        );
703        listview.append_to_builder(&mut lv_u32_u32, &mut ctx)?;
704        assert_arrays_eq!(lv_u32_u32.finish(), list, &mut ctx);
705
706        // Both source encodings appended into `ListBuilder`s with non-`u64` (including signed)
707        // offset types.
708        let mut list_builder = ListBuilder::<u32>::with_capacity_in(
709            elem_dtype(),
710            Nullable,
711            8,
712            4,
713            BufferAllocatorRef::static_ref(),
714        );
715        list.append_to_builder(&mut list_builder, &mut ctx)?;
716        assert_arrays_eq!(list_builder.finish(), list, &mut ctx);
717
718        let mut list_builder_i32 = ListBuilder::<i32>::with_capacity_in(
719            elem_dtype(),
720            Nullable,
721            8,
722            4,
723            BufferAllocatorRef::static_ref(),
724        );
725        listview.append_to_builder(&mut list_builder_i32, &mut ctx)?;
726        assert_arrays_eq!(list_builder_i32.finish(), list, &mut ctx);
727
728        Ok(())
729    }
730
731    #[test]
732    fn test_append_list_arrays_grow_builder() -> VortexResult<()> {
733        let mut ctx = array_session().create_execution_ctx();
734        let dtype: Arc<DType> = Arc::new(I32.into());
735
736        // Enough lists to exceed the offsets capacity of a zero-capacity builder, so appending
737        // must grow the builder rather than panic in `uninit_range`.
738        let lists: Vec<Option<Vec<i32>>> =
739            (0..100).map(|i| (i % 10 != 0).then(|| vec![i])).collect();
740        let source = ListArray::from_iter_opt_slow::<u32, _, _>(lists.clone(), Arc::clone(&dtype))?;
741        let expected = ListArray::from_iter_opt_slow::<u32, _, _>(
742            lists.iter().cloned().chain(lists.iter().cloned()),
743            Arc::clone(&dtype),
744        )?;
745
746        // Appending twice checks growth from a non-empty builder and offset rebasing.
747        let mut builder = ListBuilder::<u32>::with_capacity_in(
748            Arc::clone(&dtype),
749            Nullable,
750            0,
751            0,
752            BufferAllocatorRef::static_ref(),
753        );
754        builder.append_list_array(source.as_view(), &mut ctx)?;
755        builder.append_list_array(source.as_view(), &mut ctx)?;
756        assert_arrays_eq!(builder.finish(), expected, &mut ctx);
757
758        let source_listview = source.into_array().execute::<ListViewArray>(&mut ctx)?;
759        let mut builder = ListBuilder::<u32>::with_capacity_in(
760            dtype,
761            Nullable,
762            0,
763            0,
764            BufferAllocatorRef::static_ref(),
765        );
766        builder.append_listview_array(source_listview.as_view(), &mut ctx)?;
767        builder.append_listview_array(source_listview.as_view(), &mut ctx)?;
768        assert_arrays_eq!(builder.finish(), expected, &mut ctx);
769
770        Ok(())
771    }
772
773    /// A `ListArray`'s offsets can only describe contiguous, in-order lists, so an overlapping
774    /// source has to be flattened before its elements can be appended in bulk. A sliced source,
775    /// meanwhile, keeps the layout it has and is appended from wherever its first list starts.
776    #[test]
777    fn test_append_listview_array_flattens_overlaps_and_skips_leading_elements() -> VortexResult<()>
778    {
779        let mut ctx = array_session().create_execution_ctx();
780        let dtype: Arc<DType> = Arc::new(I32.into());
781
782        // Overlapping source, so not zero-copyable to a list:
783        // - List 0: [10, 20]
784        // - List 1: null (size is intentionally non-zero in the source metadata)
785        // - List 2: [10], sharing the elements list 0 already referenced
786        let overlapping = unsafe {
787            ListViewArray::new_unchecked(
788                buffer![10i32, 20, 30].into_array(),
789                buffer![0u32, 1, 0].into_array(),
790                buffer![2u8, 2, 1].into_array(),
791                Validity::from_iter([true, false, true]),
792            )
793        };
794        assert!(!overlapping.is_zero_copy_to_list());
795
796        // Zero-copyable source sliced past its first list, so its elements start at offset 2.
797        let sliced = unsafe {
798            ListViewArray::new_unchecked(
799                buffer![40i32, 50, 60, 70].into_array(),
800                buffer![0u32, 2].into_array(),
801                buffer![2u32, 2].into_array(),
802                Validity::AllValid,
803            )
804            .with_zero_copy_to_list(true)
805        }
806        .into_array()
807        .slice(1..2)?
808        .execute::<ListViewArray>(&mut ctx)?;
809
810        let mut builder = ListBuilder::<u32>::with_capacity_in(
811            dtype,
812            Nullable,
813            0,
814            0,
815            BufferAllocatorRef::static_ref(),
816        );
817        builder.append_listview_array(overlapping.as_view(), &mut ctx)?;
818        builder.append_listview_array(sliced.as_view(), &mut ctx)?;
819
820        let list = builder.finish_into_list();
821        assert_arrays_eq!(
822            list.elements(),
823            PrimitiveArray::from_iter([10i32, 20, 10, 60, 70]),
824            &mut ctx
825        );
826        assert_arrays_eq!(
827            list.offsets(),
828            PrimitiveArray::from_iter([0u32, 2, 2, 3, 5]),
829            &mut ctx
830        );
831
832        Ok(())
833    }
834
835    #[test]
836    fn test_extend_builder() {
837        test_extend_builder_gen::<i32>();
838        test_extend_builder_gen::<i64>();
839
840        test_extend_builder_gen::<u32>();
841        test_extend_builder_gen::<u64>();
842    }
843
844    #[test]
845    pub fn test_array_with_gap() {
846        let one_trailing_unused_element = ListArray::try_new(
847            buffer![1, 2, 3, 4].into_array(),
848            buffer![0, 3].into_array(),
849            Validity::NonNullable,
850        )
851        .unwrap();
852
853        let second_array = ListArray::try_new(
854            buffer![5, 6].into_array(),
855            buffer![0, 2].into_array(),
856            Validity::NonNullable,
857        )
858        .unwrap();
859
860        let chunked_list = ChunkedArray::try_new(
861            vec![
862                one_trailing_unused_element.clone().into_array(),
863                second_array.clone().into_array(),
864            ],
865            DType::List(Arc::new(DType::Primitive(I32, NonNullable)), NonNullable),
866        );
867
868        let mut ctx = array_session().create_execution_ctx();
869        let canon_values = chunked_list
870            .unwrap()
871            .as_array()
872            .clone()
873            .execute::<ListViewArray>(&mut ctx)
874            .unwrap();
875
876        assert_eq!(
877            one_trailing_unused_element
878                .execute_scalar(0, &mut array_session().create_execution_ctx())
879                .unwrap(),
880            canon_values
881                .execute_scalar(0, &mut array_session().create_execution_ctx())
882                .unwrap()
883        );
884        assert_eq!(
885            second_array
886                .execute_scalar(0, &mut array_session().create_execution_ctx())
887                .unwrap(),
888            canon_values
889                .execute_scalar(1, &mut array_session().create_execution_ctx())
890                .unwrap()
891        );
892    }
893
894    #[test]
895    fn test_append_scalar() {
896        let dtype: Arc<DType> = Arc::new(I32.into());
897        let mut builder = ListBuilder::<u64>::with_capacity_in(
898            Arc::clone(&dtype),
899            Nullable,
900            20,
901            10,
902            BufferAllocatorRef::static_ref(),
903        );
904
905        // Test appending a valid list.
906        let list_scalar1 =
907            Scalar::list(Arc::clone(&dtype), vec![1i32.into(), 2i32.into()], Nullable);
908        builder.append_scalar(&list_scalar1).unwrap();
909
910        // Test appending another list.
911        let list_scalar2 = Scalar::list(
912            Arc::clone(&dtype),
913            vec![3i32.into(), 4i32.into(), 5i32.into()],
914            Nullable,
915        );
916        builder.append_scalar(&list_scalar2).unwrap();
917
918        // Test appending null value.
919        let null_scalar = Scalar::null(DType::List(Arc::clone(&dtype), Nullable));
920        builder.append_scalar(&null_scalar).unwrap();
921
922        let array = builder.finish_into_list();
923        assert_eq!(array.len(), 3);
924
925        let mut ctx = array_session().create_execution_ctx();
926
927        // Check actual values using scalar_at.
928
929        let scalar0 = array.execute_scalar(0, &mut ctx).unwrap();
930        let list0 = scalar0.as_list();
931        assert_eq!(list0.len(), 2);
932        if let Some(list0_items) = list0.elements() {
933            assert_eq!(list0_items[0].as_primitive().typed_value::<i32>(), Some(1));
934            assert_eq!(list0_items[1].as_primitive().typed_value::<i32>(), Some(2));
935        }
936
937        let scalar1 = array.execute_scalar(1, &mut ctx).unwrap();
938        let list1 = scalar1.as_list();
939        assert_eq!(list1.len(), 3);
940        if let Some(list1_items) = list1.elements() {
941            assert_eq!(list1_items[0].as_primitive().typed_value::<i32>(), Some(3));
942            assert_eq!(list1_items[1].as_primitive().typed_value::<i32>(), Some(4));
943            assert_eq!(list1_items[2].as_primitive().typed_value::<i32>(), Some(5));
944        }
945
946        let scalar2 = array.execute_scalar(2, &mut ctx).unwrap();
947        let list2 = scalar2.as_list();
948        assert!(list2.is_null()); // This should be null.
949
950        // Check validity.
951        assert!(
952            array
953                .validity()
954                .vortex_expect("list validity should be derivable")
955                .execute_is_valid(0, &mut ctx)
956                .unwrap()
957        );
958        assert!(
959            array
960                .validity()
961                .vortex_expect("list validity should be derivable")
962                .execute_is_valid(1, &mut ctx)
963                .unwrap()
964        );
965        assert!(
966            !array
967                .validity()
968                .vortex_expect("list validity should be derivable")
969                .execute_is_valid(2, &mut ctx)
970                .unwrap()
971        );
972
973        // Test wrong dtype error.
974        let mut builder = ListBuilder::<u64>::with_capacity_in(
975            dtype,
976            NonNullable,
977            20,
978            10,
979            BufferAllocatorRef::static_ref(),
980        );
981        let wrong_scalar = Scalar::from(42i32);
982        assert!(builder.append_scalar(&wrong_scalar).is_err());
983    }
984
985    #[test]
986    fn test_append_array_as_list() {
987        let dtype: Arc<DType> = Arc::new(I32.into());
988        let mut ctx = array_session().create_execution_ctx();
989        let mut builder = ListBuilder::<u32>::with_capacity_in(
990            Arc::clone(&dtype),
991            NonNullable,
992            20,
993            10,
994            BufferAllocatorRef::static_ref(),
995        );
996
997        // Append a primitive array as a single list entry.
998        let arr1 = buffer![1i32, 2, 3].into_array();
999        builder.append_array_as_list(&arr1, &mut ctx).unwrap();
1000
1001        // Interleave with a list scalar.
1002        builder
1003            .append_value(
1004                Scalar::list(
1005                    Arc::clone(&dtype),
1006                    vec![10i32.into(), 11i32.into()],
1007                    NonNullable,
1008                )
1009                .as_list(),
1010            )
1011            .unwrap();
1012
1013        // Append another primitive array as a single list entry.
1014        let arr2 = buffer![4i32, 5].into_array();
1015        builder.append_array_as_list(&arr2, &mut ctx).unwrap();
1016
1017        // Append an empty array as a single list entry (empty list).
1018        let arr3 = buffer![0i32; 0].into_array();
1019        builder.append_array_as_list(&arr3, &mut ctx).unwrap();
1020
1021        // Interleave with another list scalar (empty list).
1022        builder
1023            .append_value(Scalar::list_empty(Arc::clone(&dtype), NonNullable).as_list())
1024            .unwrap();
1025
1026        let list = builder.finish_into_list();
1027        assert_eq!(list.len(), 5);
1028
1029        // Verify elements array: [1, 2, 3, 10, 11, 4, 5].
1030        assert_arrays_eq!(
1031            list.elements(),
1032            PrimitiveArray::from_iter([1i32, 2, 3, 10, 11, 4, 5]),
1033            &mut ctx
1034        );
1035
1036        // Verify offsets array.
1037        assert_arrays_eq!(
1038            list.offsets(),
1039            PrimitiveArray::from_iter([0u32, 3, 5, 7, 7, 7]),
1040            &mut ctx
1041        );
1042
1043        // Test dtype mismatch error.
1044        let mut builder = ListBuilder::<u32>::with_capacity_in(
1045            dtype,
1046            NonNullable,
1047            20,
1048            10,
1049            BufferAllocatorRef::static_ref(),
1050        );
1051        let wrong_dtype_arr = buffer![1i64, 2, 3].into_array();
1052        assert!(
1053            builder
1054                .append_array_as_list(&wrong_dtype_arr, &mut ctx)
1055                .is_err()
1056        );
1057    }
1058}