Skip to main content

vortex_array/builders/
listview.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! ListView Builder Implementation.
5//!
6//! A builder for [`ListViewArray`] that tracks both offsets and sizes.
7//!
8//! Unlike [`ListArray`] which only tracks offsets, [`ListViewArray`] stores both offsets and sizes
9//! in separate arrays for better compression.
10//!
11//! [`ListArray`]: crate::arrays::ListArray
12
13use std::sync::Arc;
14
15use vortex_error::VortexExpect;
16use vortex_error::VortexResult;
17use vortex_error::vortex_ensure;
18use vortex_error::vortex_panic;
19use vortex_mask::Mask;
20
21use crate::ArrayRef;
22use crate::Canonical;
23use crate::ExecutionCtx;
24use crate::array::ArrayView;
25use crate::array::IntoArray;
26use crate::arrays::List;
27use crate::arrays::ListView;
28use crate::arrays::ListViewArray;
29use crate::arrays::PrimitiveArray;
30use crate::arrays::list::ListArrayExt;
31use crate::arrays::listview::ListViewArrayExt;
32use crate::arrays::listview::ListViewRebuildMode;
33use crate::builders::ArrayBuilder;
34use crate::builders::DEFAULT_BUILDER_CAPACITY;
35use crate::builders::PrimitiveBuilder;
36use crate::builders::UninitRange;
37use crate::builders::builder_with_capacity;
38use crate::builders::lazy_null_builder::LazyBitBufferBuilder;
39use crate::builtins::ArrayBuiltins;
40use crate::dtype::DType;
41use crate::dtype::IntegerPType;
42use crate::dtype::Nullability;
43use crate::match_each_integer_ptype;
44use crate::scalar::ListScalar;
45use crate::scalar::Scalar;
46
47/// A builder for creating [`ListViewArray`] instances, parameterized by the [`IntegerPType`] of
48/// the `offsets` and the `sizes` builders.
49///
50/// This builder tracks both offsets and sizes using potentially different integer types for memory
51/// efficiency. For example, you might use `u64` for offsets but only `u8` for sizes if your lists
52/// are small.
53///
54/// Any combination of [`IntegerPType`] types are valid, as long as the type of `sizes` can fit into
55/// the type of `offsets`.
56pub struct ListViewBuilder<O: IntegerPType, S: IntegerPType> {
57    /// The [`DType`] of the [`ListViewArray`]. This **must** be a [`DType::List`].
58    dtype: DType,
59
60    /// The builder for the underlying elements of the [`ListArray`](crate::arrays::ListArray).
61    elements_builder: Box<dyn ArrayBuilder>,
62
63    /// The builder for the `offsets` into the `elements` array.
64    offsets_builder: PrimitiveBuilder<O>,
65
66    /// The builder for the `sizes` of each list view.
67    sizes_builder: PrimitiveBuilder<S>,
68
69    /// The null map builder of the [`ListViewArray`].
70    nulls: LazyBitBufferBuilder,
71}
72
73impl<O: IntegerPType, S: IntegerPType> ListViewBuilder<O, S> {
74    /// Creates a new `ListViewBuilder` with a capacity of [`DEFAULT_BUILDER_CAPACITY`].
75    pub fn new(element_dtype: Arc<DType>, nullability: Nullability) -> Self {
76        Self::with_capacity(
77            element_dtype,
78            nullability,
79            // We arbitrarily choose 2 times the number of list scalars for the capacity of the
80            // elements builder since we cannot know this ahead of time.
81            DEFAULT_BUILDER_CAPACITY * 2,
82            DEFAULT_BUILDER_CAPACITY,
83        )
84    }
85
86    /// Create a new [`ListViewArray`] builder with a with the given `capacity`, as well as an
87    /// initial capacity for the `elements` builder (since we cannot know that ahead of time solely
88    /// based on the outer array `capacity`).
89    ///
90    /// # Panics
91    ///
92    /// Panics if the size type `S` cannot fit within the offset type `O`.
93    pub fn with_capacity(
94        element_dtype: Arc<DType>,
95        nullability: Nullability,
96        elements_capacity: usize,
97        capacity: usize,
98    ) -> Self {
99        let elements_builder = builder_with_capacity(&element_dtype, elements_capacity);
100
101        let offsets_builder =
102            PrimitiveBuilder::<O>::with_capacity(Nullability::NonNullable, capacity);
103        let sizes_builder =
104            PrimitiveBuilder::<S>::with_capacity(Nullability::NonNullable, capacity);
105
106        let nulls = LazyBitBufferBuilder::new(capacity);
107
108        Self {
109            dtype: DType::List(element_dtype, nullability),
110            elements_builder,
111            offsets_builder,
112            sizes_builder,
113            nulls,
114        }
115    }
116
117    /// Appends an array as a single non-null list entry to the builder.
118    ///
119    /// The input `array` must have the same dtype as the element dtype of this list builder.
120    ///
121    /// Note that the list entry will be non-null but the elements themselves are allowed to be null
122    /// (only if the elements [`DType`] is nullable, of course).
123    pub fn append_array_as_list(
124        &mut self,
125        array: &ArrayRef,
126        ctx: &mut ExecutionCtx,
127    ) -> VortexResult<()> {
128        vortex_ensure!(
129            array.dtype() == self.element_dtype(),
130            "Array dtype {:?} does not match list element dtype {:?}",
131            array.dtype(),
132            self.element_dtype()
133        );
134
135        let curr_offset = self.elements_builder.len();
136        let num_elements = array.len();
137
138        // We must assert this even in release mode to ensure that the safety comment in
139        // `finish_into_listview` is correct.
140        assert!(
141            ((curr_offset + num_elements) as u64) < O::max_value_as_u64(),
142            "appending this list would cause an offset overflow"
143        );
144
145        self.elements_builder.reserve_exact(num_elements);
146        array.append_to_builder(self.elements_builder.as_mut(), ctx)?;
147        self.nulls.append_non_null();
148
149        self.offsets_builder.append_value(
150            O::from_usize(curr_offset).vortex_expect("Failed to convert from usize to `O`"),
151        );
152        self.sizes_builder.append_value(
153            S::from_usize(num_elements).vortex_expect("Failed to convert from usize to `S`"),
154        );
155
156        Ok(())
157    }
158
159    /// Append a list of values to the builder.
160    ///
161    /// This method extends the value builder with the provided values and records
162    /// the offset and size of the new list.
163    pub fn append_value(&mut self, value: ListScalar) -> VortexResult<()> {
164        let Some(elements) = value.elements() else {
165            // If `elements` is `None`, then the `value` is a null value.
166            vortex_ensure!(
167                self.dtype.is_nullable(),
168                "Cannot append null value to non-nullable list builder"
169            );
170            self.append_null();
171            return Ok(());
172        };
173
174        let curr_offset = self.elements_builder.len();
175        let num_elements = elements.len();
176
177        // We must assert this even in release mode to ensure that the safety comment in
178        // `finish_into_listview` is correct.
179        assert!(
180            ((curr_offset + num_elements) as u64) < O::max_value_as_u64(),
181            "appending this list would cause an offset overflow"
182        );
183
184        for scalar in elements {
185            self.elements_builder.append_scalar(&scalar)?;
186        }
187        self.nulls.append_non_null();
188
189        self.offsets_builder.append_value(
190            O::from_usize(curr_offset).vortex_expect("Failed to convert from usize to `O`"),
191        );
192        self.sizes_builder.append_value(
193            S::from_usize(num_elements).vortex_expect("Failed to convert from usize to `S`"),
194        );
195
196        Ok(())
197    }
198
199    /// Finishes the builder directly into a [`ListViewArray`].
200    pub fn finish_into_listview(&mut self) -> ListViewArray {
201        debug_assert_eq!(self.offsets_builder.len(), self.sizes_builder.len());
202        debug_assert_eq!(self.offsets_builder.len(), self.nulls.len());
203
204        let elements = self.elements_builder.finish();
205        let offsets = self.offsets_builder.finish();
206        let sizes = self.sizes_builder.finish();
207        let validity = self.nulls.finish_with_nullability(self.dtype.nullability());
208
209        // SAFETY:
210        // - Both the offsets and the sizes are non-nullable.
211        // - The offsets, sizes, and validity have the same length since we always appended the same
212        //   amount.
213        // - We checked on construction that the sizes type fits into the offsets.
214        // - In every method that adds values to this builder (`append_value`, `append_scalar`,
215        //   `append_list_array`, and `append_listview_array`), we checked that `offset + size`
216        //   does not overflow.
217        // - We constructed everything in a way that builds the `ListViewArray` similar to the shape
218        //   of a `ListArray`, so we know the resulting array is zero-copyable to a `ListArray`.
219        unsafe {
220            ListViewArray::new_unchecked(elements, offsets, sizes, validity)
221                .with_zero_copy_to_list(true)
222        }
223    }
224
225    /// The [`DType`] of the inner elements. Note that this is **not** the same as the [`DType`] of
226    /// the outer `FixedSizeList`.
227    pub fn element_dtype(&self) -> &DType {
228        let DType::List(element_dtype, ..) = &self.dtype else {
229            vortex_panic!("`ListViewBuilder` has an incorrect dtype: {}", self.dtype);
230        };
231
232        element_dtype
233    }
234}
235
236impl<O: IntegerPType, S: IntegerPType> ArrayBuilder for ListViewBuilder<O, S> {
237    fn as_any(&self) -> &dyn std::any::Any {
238        self
239    }
240
241    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
242        self
243    }
244
245    fn dtype(&self) -> &DType {
246        &self.dtype
247    }
248
249    fn len(&self) -> usize {
250        self.offsets_builder.len()
251    }
252
253    fn append_zeros(&mut self, n: usize) {
254        debug_assert_eq!(self.offsets_builder.len(), self.sizes_builder.len());
255        debug_assert_eq!(self.offsets_builder.len(), self.nulls.len());
256
257        // Get the current position in the elements array.
258        let curr_offset = self.elements_builder.len();
259
260        // Since we consider the "zero" element of a list an empty list, we simply update the
261        // `offsets` and `sizes` metadata to add an empty list.
262        for _ in 0..n {
263            self.offsets_builder.append_value(
264                O::from_usize(curr_offset).vortex_expect("Failed to convert from usize to `O`"),
265            );
266            self.sizes_builder.append_value(S::zero());
267        }
268
269        self.nulls.append_n_non_nulls(n);
270    }
271
272    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
273        debug_assert_eq!(self.offsets_builder.len(), self.sizes_builder.len());
274        debug_assert_eq!(self.offsets_builder.len(), self.nulls.len());
275
276        // Get the current position in the elements array.
277        let curr_offset = self.elements_builder.len();
278
279        // A null list can have any representation, but we choose to use the zero representation.
280        for _ in 0..n {
281            self.offsets_builder.append_value(
282                O::from_usize(curr_offset).vortex_expect("Failed to convert from usize to `O`"),
283            );
284            self.sizes_builder.append_value(S::zero());
285        }
286
287        // This is the only difference from `append_zeros`.
288        self.nulls.append_n_nulls(n);
289    }
290
291    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
292        vortex_ensure!(
293            scalar.dtype() == self.dtype(),
294            "ListViewBuilder expected scalar with dtype {}, got {}",
295            self.dtype(),
296            scalar.dtype()
297        );
298
299        let list_scalar = scalar.as_list();
300        self.append_value(list_scalar)
301    }
302
303    fn reserve_exact(&mut self, capacity: usize) {
304        self.elements_builder.reserve_exact(capacity * 2);
305        self.offsets_builder.reserve_exact(capacity);
306        self.sizes_builder.reserve_exact(capacity);
307        self.nulls.reserve_exact(capacity);
308    }
309
310    unsafe fn set_validity_unchecked(&mut self, validity: Mask) {
311        self.nulls = LazyBitBufferBuilder::from_validity_mask(validity);
312    }
313
314    fn finish(&mut self) -> ArrayRef {
315        self.finish_into_listview().into_array()
316    }
317
318    fn finish_into_canonical(&mut self, _ctx: &mut ExecutionCtx) -> Canonical {
319        Canonical::List(self.finish_into_listview())
320    }
321
322    fn append_list_array(
323        &mut self,
324        array: ArrayView<'_, List>,
325        ctx: &mut ExecutionCtx,
326    ) -> VortexResult<()> {
327        if array.is_empty() {
328            return Ok(());
329        }
330
331        self.nulls
332            .append_validity_mask(&array.validity()?.execute_mask(array.len(), ctx)?);
333
334        let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
335        match_each_integer_ptype!(offsets.ptype(), |OffsetType| {
336            extend_from_list(
337                self,
338                array.elements(),
339                offsets.as_slice::<OffsetType>(),
340                ctx,
341            )?
342        });
343        Ok(())
344    }
345
346    fn append_listview_array(
347        &mut self,
348        array: ArrayView<'_, ListView>,
349        ctx: &mut ExecutionCtx,
350    ) -> VortexResult<()> {
351        if array.is_empty() {
352            return Ok(());
353        }
354
355        // Normalize to an exact zero-copy-to-list layout and then bulk append. This avoids the
356        // very expensive scalar_at-per-list path for overlapping / out-of-order list views.
357        let listview = array
358            .into_owned()
359            .rebuild(ListViewRebuildMode::MakeExact, ctx)?;
360        debug_assert!(listview.is_zero_copy_to_list());
361
362        self.nulls
363            .append_validity_mask(&array.validity()?.execute_mask(array.len(), ctx)?);
364
365        // Bulk append the new elements (which should have no gaps or overlaps).
366        let old_elements_len = self.elements_builder.len();
367        self.elements_builder
368            .reserve_exact(listview.elements().len());
369        listview
370            .elements()
371            .append_to_builder(self.elements_builder.as_mut(), ctx)?;
372        let new_elements_len = self.elements_builder.len();
373
374        // Reserve enough space for the new views.
375        let extend_length = listview.len();
376        self.sizes_builder.reserve_exact(extend_length);
377        self.offsets_builder.reserve_exact(extend_length);
378
379        // The incoming sizes might have a different type than the builder, so we need to cast.
380        let cast_sizes = listview
381            .sizes()
382            .clone()
383            .cast(self.sizes_builder.dtype().clone())?;
384        cast_sizes.append_to_builder(&mut self.sizes_builder, ctx)?;
385
386        // Now we need to adjust all of the offsets by adding the current number of elements in the
387        // builder.
388        let uninit_range = self.offsets_builder.uninit_range(extend_length);
389
390        // This should be cheap because we didn't compress after rebuilding.
391        let new_offsets = listview.offsets().clone().execute::<PrimitiveArray>(ctx)?;
392
393        match_each_integer_ptype!(new_offsets.ptype(), |A| {
394            adjust_and_extend_offsets::<O, A>(
395                uninit_range,
396                new_offsets,
397                old_elements_len,
398                new_elements_len,
399            );
400        });
401        Ok(())
402    }
403}
404
405/// Appends `ListArray`-layout lists (`n + 1` cumulative offsets) into a [`ListViewBuilder`].
406///
407/// Lists in a `ListArray` are contiguous, so the referenced elements can be appended in bulk,
408/// with the offsets rebased onto the builder's elements and the sizes taken from consecutive
409/// offset differences.
410fn extend_from_list<O, S, OffsetType>(
411    builder: &mut ListViewBuilder<O, S>,
412    elements: &ArrayRef,
413    offsets: &[OffsetType],
414    ctx: &mut ExecutionCtx,
415) -> VortexResult<()>
416where
417    O: IntegerPType,
418    S: IntegerPType,
419    OffsetType: IntegerPType,
420{
421    let num_lists = offsets.len() - 1;
422    let first: usize = offsets[0].as_();
423    let last: usize = offsets[num_lists].as_();
424
425    let elements_base = builder.elements_builder.len();
426
427    // We must assert this even in release mode to ensure that the safety comment in
428    // `finish_into_listview` is correct.
429    assert!(
430        ((elements_base + (last - first)) as u64) < O::max_value_as_u64(),
431        "appending this list would cause an offset overflow"
432    );
433
434    if last > first {
435        builder.elements_builder.reserve_exact(last - first);
436        elements
437            .slice(first..last)?
438            .append_to_builder(builder.elements_builder.as_mut(), ctx)?;
439    }
440
441    builder.offsets_builder.reserve_exact(num_lists);
442    builder.sizes_builder.reserve_exact(num_lists);
443    let mut offsets_range = builder.offsets_builder.uninit_range(num_lists);
444    let mut sizes_range = builder.sizes_builder.uninit_range(num_lists);
445    for i in 0..num_lists {
446        let start: usize = offsets[i].as_();
447        let end: usize = offsets[i + 1].as_();
448        offsets_range.set_value(
449            i,
450            O::from_usize(start - first + elements_base)
451                .vortex_expect("Failed to convert from usize to `O`"),
452        );
453        sizes_range.set_value(
454            i,
455            S::from_usize(end - start).vortex_expect("Failed to convert from usize to `S`"),
456        );
457    }
458    // SAFETY: We have initialized all `num_lists` values in both ranges, and both the `offsets`
459    // and the `sizes` builders are non-nullable.
460    unsafe { offsets_range.finish() };
461    unsafe { sizes_range.finish() };
462    Ok(())
463}
464
465/// Given new offsets, adds them to the `UninitRange` after adding the `old_elements_len` to each
466/// offset.
467fn adjust_and_extend_offsets<O: IntegerPType, A: IntegerPType>(
468    mut uninit_range: UninitRange<O>,
469    new_offsets: PrimitiveArray,
470    old_elements_len: usize,
471    new_elements_len: usize,
472) {
473    let new_offsets_slice = new_offsets.as_slice::<A>();
474    let old_elements_len = O::from_usize(old_elements_len)
475        .vortex_expect("the old elements length did not fit into the offset type (impossible)");
476    let new_elements_len = O::from_usize(new_elements_len)
477        .vortex_expect("the current elements length did not fit into the offset type (impossible)");
478
479    for i in 0..uninit_range.len() {
480        let new_offset = O::from_usize(
481            new_offsets_slice[i]
482                .to_usize()
483                .vortex_expect("Offsets must always fit in usize"),
484        )
485        .vortex_expect("New offset somehow did not fit into the builder's offset type");
486
487        // We have to check this even in release mode to ensure the final `new_unchecked`
488        // construction in `finish_into_listview` is valid.
489        let adjusted_new_offset = new_offset + old_elements_len;
490        assert!(
491            adjusted_new_offset <= new_elements_len,
492            "[{i}/{}]: {new_offset} + {old_elements_len} \
493                = {adjusted_new_offset} <= {new_elements_len} failed",
494            uninit_range.len()
495        );
496
497        uninit_range.set_value(i, adjusted_new_offset);
498    }
499
500    // SAFETY: We have set all the values in the range, and since `offsets` are non-nullable, we are
501    // done.
502    unsafe { uninit_range.finish() };
503}
504
505#[cfg(test)]
506mod tests {
507    use std::sync::Arc;
508
509    use vortex_buffer::buffer;
510    use vortex_error::VortexExpect;
511    use vortex_error::VortexResult;
512
513    use super::ListViewBuilder;
514    use crate::IntoArray;
515    use crate::VortexSessionExecute;
516    use crate::array_session;
517    use crate::arrays::ListArray;
518    use crate::arrays::ListViewArray;
519    use crate::arrays::listview::ListViewArrayExt;
520    use crate::assert_arrays_eq;
521    use crate::builders::ArrayBuilder;
522    use crate::builders::listview::PrimitiveArray;
523    use crate::dtype::DType;
524    use crate::dtype::Nullability::NonNullable;
525    use crate::dtype::Nullability::Nullable;
526    use crate::dtype::PType::I32;
527    use crate::scalar::Scalar;
528    use crate::validity::Validity;
529
530    #[test]
531    fn test_empty() {
532        let mut builder =
533            ListViewBuilder::<u32, u32>::with_capacity(Arc::new(I32.into()), NonNullable, 0, 0);
534
535        let listview = builder.finish();
536        assert_eq!(listview.len(), 0);
537    }
538
539    #[test]
540    fn test_basic_append_and_nulls() {
541        let mut ctx = array_session().create_execution_ctx();
542        let dtype: Arc<DType> = Arc::new(I32.into());
543        let mut builder =
544            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
545
546        // Append a regular list.
547        builder
548            .append_value(
549                Scalar::list(
550                    Arc::clone(&dtype),
551                    vec![1i32.into(), 2i32.into(), 3i32.into()],
552                    NonNullable,
553                )
554                .as_list(),
555            )
556            .unwrap();
557
558        // Append an empty list.
559        builder
560            .append_value(Scalar::list_empty(Arc::clone(&dtype), NonNullable).as_list())
561            .unwrap();
562
563        // Append a null list.
564        builder.append_null();
565
566        // Append another regular list.
567        builder
568            .append_value(
569                Scalar::list(dtype, vec![4i32.into(), 5i32.into()], NonNullable).as_list(),
570            )
571            .unwrap();
572
573        let listview = builder.finish_into_listview();
574        assert_eq!(listview.len(), 4);
575
576        // Check first list: [1, 2, 3].
577        assert_arrays_eq!(
578            listview.list_elements_at(0).unwrap(),
579            PrimitiveArray::from_iter([1i32, 2, 3]),
580            &mut ctx
581        );
582
583        // Check empty list.
584        assert_eq!(listview.list_elements_at(1).unwrap().len(), 0);
585
586        // Check null list.
587        assert!(
588            !listview
589                .validity()
590                .vortex_expect("listview validity should be derivable")
591                .execute_is_valid(2, &mut ctx)
592                .unwrap()
593        );
594
595        // Check last list: [4, 5].
596        assert_arrays_eq!(
597            listview.list_elements_at(3).unwrap(),
598            PrimitiveArray::from_iter([4i32, 5]),
599            &mut ctx
600        );
601    }
602
603    #[test]
604    fn test_different_offset_size_types() {
605        let mut ctx = array_session().create_execution_ctx();
606        // Test u32 offsets with u8 sizes.
607        let dtype: Arc<DType> = Arc::new(I32.into());
608        let mut builder =
609            ListViewBuilder::<u32, u8>::with_capacity(Arc::clone(&dtype), NonNullable, 0, 0);
610
611        builder
612            .append_value(
613                Scalar::list(
614                    Arc::clone(&dtype),
615                    vec![1i32.into(), 2i32.into()],
616                    NonNullable,
617                )
618                .as_list(),
619            )
620            .unwrap();
621
622        builder
623            .append_value(
624                Scalar::list(
625                    dtype,
626                    vec![3i32.into(), 4i32.into(), 5i32.into()],
627                    NonNullable,
628                )
629                .as_list(),
630            )
631            .unwrap();
632
633        let listview = builder.finish_into_listview();
634        assert_eq!(listview.len(), 2);
635
636        // Verify first list: [1, 2].
637        assert_arrays_eq!(
638            listview.list_elements_at(0).unwrap(),
639            PrimitiveArray::from_iter([1i32, 2]),
640            &mut ctx
641        );
642
643        // Verify second list: [3, 4, 5].
644        assert_arrays_eq!(
645            listview.list_elements_at(1).unwrap(),
646            PrimitiveArray::from_iter([3i32, 4, 5]),
647            &mut ctx
648        );
649
650        // Test u64 offsets with u16 sizes.
651        let dtype2: Arc<DType> = Arc::new(I32.into());
652        let mut builder2 =
653            ListViewBuilder::<u64, u16>::with_capacity(Arc::clone(&dtype2), NonNullable, 0, 0);
654
655        for i in 0..5 {
656            builder2
657                .append_value(
658                    Scalar::list(Arc::clone(&dtype2), vec![(i * 10).into()], NonNullable).as_list(),
659                )
660                .unwrap();
661        }
662
663        let listview2 = builder2.finish_into_listview();
664        assert_eq!(listview2.len(), 5);
665
666        // Verify the values: [0], [10], [20], [30], [40].
667        for i in 0..5i32 {
668            assert_arrays_eq!(
669                listview2.list_elements_at(i as usize).unwrap(),
670                PrimitiveArray::from_iter([i * 10]),
671                &mut ctx
672            );
673        }
674    }
675
676    #[test]
677    fn test_builder_trait_methods() {
678        let mut ctx = array_session().create_execution_ctx();
679        let dtype: Arc<DType> = Arc::new(I32.into());
680        let mut builder =
681            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
682
683        // Test append_zeros (creates empty lists).
684        builder.append_zeros(2);
685        assert_eq!(builder.len(), 2);
686
687        // Test append_nulls.
688        unsafe {
689            builder.append_nulls_unchecked(2);
690        }
691        assert_eq!(builder.len(), 4);
692
693        // Test append_scalar.
694        let list_scalar = Scalar::list(dtype, vec![10i32.into(), 20i32.into()], Nullable);
695        builder.append_scalar(&list_scalar).unwrap();
696        assert_eq!(builder.len(), 5);
697
698        let listview = builder.finish_into_listview();
699        assert_eq!(listview.len(), 5);
700
701        // First two are empty lists (from append_zeros).
702        assert_eq!(listview.list_elements_at(0).unwrap().len(), 0);
703        assert_eq!(listview.list_elements_at(1).unwrap().len(), 0);
704
705        // Next two are nulls.
706        assert!(
707            !listview
708                .validity()
709                .vortex_expect("listview validity should be derivable")
710                .execute_is_valid(2, &mut ctx)
711                .unwrap()
712        );
713        assert!(
714            !listview
715                .validity()
716                .vortex_expect("listview validity should be derivable")
717                .execute_is_valid(3, &mut ctx)
718                .unwrap()
719        );
720
721        // Last is the regular list: [10, 20].
722        assert_arrays_eq!(
723            listview.list_elements_at(4).unwrap(),
724            PrimitiveArray::from_iter([10i32, 20]),
725            &mut ctx
726        );
727    }
728
729    #[test]
730    fn test_extend_from_array() {
731        let mut ctx = array_session().create_execution_ctx();
732        let dtype: Arc<DType> = Arc::new(I32.into());
733
734        // Create a source ListArray.
735        let source = ListArray::from_iter_opt_slow::<u32, _, Vec<i32>>(
736            [Some(vec![1, 2, 3]), None, Some(vec![4, 5])],
737            Arc::new(I32.into()),
738        )
739        .unwrap();
740
741        let mut builder =
742            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
743
744        // Add initial data.
745        builder
746            .append_value(Scalar::list(dtype, vec![0i32.into()], NonNullable).as_list())
747            .unwrap();
748
749        // Extend from the ListArray.
750        let source = source
751            .into_array()
752            .execute::<ListViewArray>(&mut ctx)
753            .unwrap();
754        builder
755            .append_listview_array(source.as_view(), &mut ctx)
756            .unwrap();
757
758        // Extend from empty array (should be no-op).
759        let empty_source = ListArray::from_iter_opt_slow::<u32, _, Vec<i32>>(
760            std::iter::empty::<Option<Vec<i32>>>(),
761            Arc::new(I32.into()),
762        )
763        .unwrap();
764        let empty_source = empty_source
765            .into_array()
766            .execute::<ListViewArray>(&mut ctx)
767            .unwrap();
768        builder
769            .append_listview_array(empty_source.as_view(), &mut ctx)
770            .unwrap();
771
772        let listview = builder.finish_into_listview();
773        assert_eq!(listview.len(), 4);
774
775        // Check the extended data.
776        // First list: [0] (initial data).
777        assert_arrays_eq!(
778            listview.list_elements_at(0).unwrap(),
779            PrimitiveArray::from_iter([0i32]),
780            &mut ctx
781        );
782
783        // Second list: [1, 2, 3] (from source).
784        assert_arrays_eq!(
785            listview.list_elements_at(1).unwrap(),
786            PrimitiveArray::from_iter([1i32, 2, 3]),
787            &mut ctx
788        );
789
790        // Third list: null (from source).
791        assert!(
792            !listview
793                .validity()
794                .vortex_expect("listview validity should be derivable")
795                .execute_is_valid(2, &mut ctx)
796                .unwrap()
797        );
798
799        // Fourth list: [4, 5] (from source).
800        assert_arrays_eq!(
801            listview.list_elements_at(3).unwrap(),
802            PrimitiveArray::from_iter([4i32, 5]),
803            &mut ctx
804        );
805    }
806
807    #[test]
808    fn test_append_list_array_grows_builder() -> VortexResult<()> {
809        let mut ctx = array_session().create_execution_ctx();
810        let dtype: Arc<DType> = Arc::new(I32.into());
811
812        // Enough lists to exceed the offsets/sizes capacity of a zero-capacity builder, so
813        // appending must grow the builder rather than panic in `uninit_range`.
814        let lists: Vec<Option<Vec<i32>>> =
815            (0..100).map(|i| (i % 10 != 0).then(|| vec![i])).collect();
816        let source = ListArray::from_iter_opt_slow::<u32, _, _>(lists.clone(), Arc::clone(&dtype))?;
817
818        let mut builder =
819            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
820        builder.append_list_array(source.as_view(), &mut ctx)?;
821        // Append a second time to check growth from a non-empty builder and offset rebasing.
822        builder.append_list_array(source.as_view(), &mut ctx)?;
823
824        let listview = builder.finish_into_listview();
825        assert!(listview.is_zero_copy_to_list());
826
827        let expected = ListArray::from_iter_opt_slow::<u32, _, _>(
828            lists.iter().cloned().chain(lists.iter().cloned()),
829            dtype,
830        )?;
831        assert_arrays_eq!(listview, expected, &mut ctx);
832
833        Ok(())
834    }
835
836    #[test]
837    fn test_extend_from_array_overlapping_listview() {
838        let mut ctx = array_session().create_execution_ctx();
839        let dtype: Arc<DType> = Arc::new(I32.into());
840
841        // Non-ZCTL source:
842        // - List 0: [10, 20]
843        // - List 1: null (size is intentionally non-zero in source metadata)
844        // - List 2: [10]
845        let source = unsafe {
846            ListViewArray::new_unchecked(
847                buffer![10i32, 20, 30].into_array(),
848                buffer![0u32, 1, 0].into_array(),
849                buffer![2u8, 2, 1].into_array(),
850                Validity::from_iter([true, false, true]),
851            )
852        };
853        assert!(!source.is_zero_copy_to_list());
854
855        let mut builder =
856            ListViewBuilder::<u32, u8>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
857        builder
858            .append_listview_array(source.as_view(), &mut ctx)
859            .unwrap();
860
861        let listview = builder.finish_into_listview();
862        assert_eq!(listview.len(), 3);
863        assert!(listview.is_zero_copy_to_list());
864
865        assert_arrays_eq!(
866            listview.list_elements_at(0).unwrap(),
867            PrimitiveArray::from_iter([10i32, 20]),
868            &mut ctx
869        );
870        assert!(
871            !listview
872                .validity()
873                .vortex_expect("listview validity should be derivable")
874                .execute_is_valid(1, &mut ctx)
875                .unwrap()
876        );
877        assert_eq!(listview.list_elements_at(1).unwrap().len(), 0);
878        assert_arrays_eq!(
879            listview.list_elements_at(2).unwrap(),
880            PrimitiveArray::from_iter([10i32]),
881            &mut ctx
882        );
883    }
884
885    #[test]
886    fn test_error_append_null_to_non_nullable() {
887        let dtype: Arc<DType> = Arc::new(I32.into());
888        let mut builder =
889            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), NonNullable, 0, 0);
890
891        // Create a null list with nullable type (since Scalar::null requires nullable type).
892        let null_scalar = Scalar::null(DType::List(dtype, Nullable));
893        let null_list = null_scalar.as_list();
894
895        // This should fail because we're trying to append a null to a non-nullable builder.
896        let result = builder.append_value(null_list);
897        assert!(result.is_err());
898        assert!(
899            result
900                .unwrap_err()
901                .to_string()
902                .contains("null value to non-nullable")
903        );
904    }
905
906    #[test]
907    fn test_append_array_as_list() {
908        let dtype: Arc<DType> = Arc::new(I32.into());
909        let mut ctx = array_session().create_execution_ctx();
910        let mut builder =
911            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), NonNullable, 20, 10);
912
913        // Append a primitive array as a single list entry.
914        let arr1 = buffer![1i32, 2, 3].into_array();
915        builder.append_array_as_list(&arr1, &mut ctx).unwrap();
916
917        // Interleave with a list scalar.
918        builder
919            .append_value(
920                Scalar::list(
921                    Arc::clone(&dtype),
922                    vec![10i32.into(), 11i32.into()],
923                    NonNullable,
924                )
925                .as_list(),
926            )
927            .unwrap();
928
929        // Append another primitive array as a single list entry.
930        let arr2 = buffer![4i32, 5].into_array();
931        builder.append_array_as_list(&arr2, &mut ctx).unwrap();
932
933        // Append an empty array as a single list entry (empty list).
934        let arr3 = buffer![0i32; 0].into_array();
935        builder.append_array_as_list(&arr3, &mut ctx).unwrap();
936
937        // Interleave with another list scalar.
938        builder
939            .append_value(Scalar::list_empty(Arc::clone(&dtype), NonNullable).as_list())
940            .unwrap();
941
942        let listview = builder.finish_into_listview();
943        assert_eq!(listview.len(), 5);
944
945        // Verify elements array: [1, 2, 3, 10, 11, 4, 5].
946        assert_arrays_eq!(
947            listview.elements(),
948            PrimitiveArray::from_iter([1i32, 2, 3, 10, 11, 4, 5]),
949            &mut ctx
950        );
951
952        // Verify offsets array.
953        assert_arrays_eq!(
954            listview.offsets(),
955            PrimitiveArray::from_iter([0u32, 3, 5, 7, 7]),
956            &mut ctx
957        );
958
959        // Verify sizes array.
960        assert_arrays_eq!(
961            listview.sizes(),
962            PrimitiveArray::from_iter([3u32, 2, 2, 0, 0]),
963            &mut ctx
964        );
965
966        // Test dtype mismatch error.
967        let mut builder = ListViewBuilder::<u32, u32>::with_capacity(dtype, NonNullable, 20, 10);
968        let wrong_dtype_arr = buffer![1i64, 2, 3].into_array();
969        assert!(
970            builder
971                .append_array_as_list(&wrong_dtype_arr, &mut ctx)
972                .is_err()
973        );
974    }
975}