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::ListArraySlotsExt;
31use crate::arrays::listview::ListViewArraySlotsExt;
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::arrays::listview::ListViewArraySlotsExt;
521    use crate::assert_arrays_eq;
522    use crate::builders::ArrayBuilder;
523    use crate::builders::listview::PrimitiveArray;
524    use crate::dtype::DType;
525    use crate::dtype::Nullability::NonNullable;
526    use crate::dtype::Nullability::Nullable;
527    use crate::dtype::PType::I32;
528    use crate::scalar::Scalar;
529    use crate::validity::Validity;
530
531    #[test]
532    fn test_empty() {
533        let mut builder =
534            ListViewBuilder::<u32, u32>::with_capacity(Arc::new(I32.into()), NonNullable, 0, 0);
535
536        let listview = builder.finish();
537        assert_eq!(listview.len(), 0);
538    }
539
540    #[test]
541    fn test_basic_append_and_nulls() {
542        let mut ctx = array_session().create_execution_ctx();
543        let dtype: Arc<DType> = Arc::new(I32.into());
544        let mut builder =
545            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
546
547        // Append a regular list.
548        builder
549            .append_value(
550                Scalar::list(
551                    Arc::clone(&dtype),
552                    vec![1i32.into(), 2i32.into(), 3i32.into()],
553                    NonNullable,
554                )
555                .as_list(),
556            )
557            .unwrap();
558
559        // Append an empty list.
560        builder
561            .append_value(Scalar::list_empty(Arc::clone(&dtype), NonNullable).as_list())
562            .unwrap();
563
564        // Append a null list.
565        builder.append_null();
566
567        // Append another regular list.
568        builder
569            .append_value(
570                Scalar::list(dtype, vec![4i32.into(), 5i32.into()], NonNullable).as_list(),
571            )
572            .unwrap();
573
574        let listview = builder.finish_into_listview();
575        assert_eq!(listview.len(), 4);
576
577        // Check first list: [1, 2, 3].
578        assert_arrays_eq!(
579            listview.list_elements_at(0).unwrap(),
580            PrimitiveArray::from_iter([1i32, 2, 3]),
581            &mut ctx
582        );
583
584        // Check empty list.
585        assert_eq!(listview.list_elements_at(1).unwrap().len(), 0);
586
587        // Check null list.
588        assert!(
589            !listview
590                .validity()
591                .vortex_expect("listview validity should be derivable")
592                .execute_is_valid(2, &mut ctx)
593                .unwrap()
594        );
595
596        // Check last list: [4, 5].
597        assert_arrays_eq!(
598            listview.list_elements_at(3).unwrap(),
599            PrimitiveArray::from_iter([4i32, 5]),
600            &mut ctx
601        );
602    }
603
604    #[test]
605    fn test_different_offset_size_types() {
606        let mut ctx = array_session().create_execution_ctx();
607        // Test u32 offsets with u8 sizes.
608        let dtype: Arc<DType> = Arc::new(I32.into());
609        let mut builder =
610            ListViewBuilder::<u32, u8>::with_capacity(Arc::clone(&dtype), NonNullable, 0, 0);
611
612        builder
613            .append_value(
614                Scalar::list(
615                    Arc::clone(&dtype),
616                    vec![1i32.into(), 2i32.into()],
617                    NonNullable,
618                )
619                .as_list(),
620            )
621            .unwrap();
622
623        builder
624            .append_value(
625                Scalar::list(
626                    dtype,
627                    vec![3i32.into(), 4i32.into(), 5i32.into()],
628                    NonNullable,
629                )
630                .as_list(),
631            )
632            .unwrap();
633
634        let listview = builder.finish_into_listview();
635        assert_eq!(listview.len(), 2);
636
637        // Verify first list: [1, 2].
638        assert_arrays_eq!(
639            listview.list_elements_at(0).unwrap(),
640            PrimitiveArray::from_iter([1i32, 2]),
641            &mut ctx
642        );
643
644        // Verify second list: [3, 4, 5].
645        assert_arrays_eq!(
646            listview.list_elements_at(1).unwrap(),
647            PrimitiveArray::from_iter([3i32, 4, 5]),
648            &mut ctx
649        );
650
651        // Test u64 offsets with u16 sizes.
652        let dtype2: Arc<DType> = Arc::new(I32.into());
653        let mut builder2 =
654            ListViewBuilder::<u64, u16>::with_capacity(Arc::clone(&dtype2), NonNullable, 0, 0);
655
656        for i in 0..5 {
657            builder2
658                .append_value(
659                    Scalar::list(Arc::clone(&dtype2), vec![(i * 10).into()], NonNullable).as_list(),
660                )
661                .unwrap();
662        }
663
664        let listview2 = builder2.finish_into_listview();
665        assert_eq!(listview2.len(), 5);
666
667        // Verify the values: [0], [10], [20], [30], [40].
668        for i in 0..5i32 {
669            assert_arrays_eq!(
670                listview2.list_elements_at(i as usize).unwrap(),
671                PrimitiveArray::from_iter([i * 10]),
672                &mut ctx
673            );
674        }
675    }
676
677    #[test]
678    fn test_builder_trait_methods() {
679        let mut ctx = array_session().create_execution_ctx();
680        let dtype: Arc<DType> = Arc::new(I32.into());
681        let mut builder =
682            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
683
684        // Test append_zeros (creates empty lists).
685        builder.append_zeros(2);
686        assert_eq!(builder.len(), 2);
687
688        // Test append_nulls.
689        unsafe {
690            builder.append_nulls_unchecked(2);
691        }
692        assert_eq!(builder.len(), 4);
693
694        // Test append_scalar.
695        let list_scalar = Scalar::list(dtype, vec![10i32.into(), 20i32.into()], Nullable);
696        builder.append_scalar(&list_scalar).unwrap();
697        assert_eq!(builder.len(), 5);
698
699        let listview = builder.finish_into_listview();
700        assert_eq!(listview.len(), 5);
701
702        // First two are empty lists (from append_zeros).
703        assert_eq!(listview.list_elements_at(0).unwrap().len(), 0);
704        assert_eq!(listview.list_elements_at(1).unwrap().len(), 0);
705
706        // Next two are nulls.
707        assert!(
708            !listview
709                .validity()
710                .vortex_expect("listview validity should be derivable")
711                .execute_is_valid(2, &mut ctx)
712                .unwrap()
713        );
714        assert!(
715            !listview
716                .validity()
717                .vortex_expect("listview validity should be derivable")
718                .execute_is_valid(3, &mut ctx)
719                .unwrap()
720        );
721
722        // Last is the regular list: [10, 20].
723        assert_arrays_eq!(
724            listview.list_elements_at(4).unwrap(),
725            PrimitiveArray::from_iter([10i32, 20]),
726            &mut ctx
727        );
728    }
729
730    #[test]
731    fn test_extend_from_array() {
732        let mut ctx = array_session().create_execution_ctx();
733        let dtype: Arc<DType> = Arc::new(I32.into());
734
735        // Create a source ListArray.
736        let source = ListArray::from_iter_opt_slow::<u32, _, Vec<i32>>(
737            [Some(vec![1, 2, 3]), None, Some(vec![4, 5])],
738            Arc::new(I32.into()),
739        )
740        .unwrap();
741
742        let mut builder =
743            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
744
745        // Add initial data.
746        builder
747            .append_value(Scalar::list(dtype, vec![0i32.into()], NonNullable).as_list())
748            .unwrap();
749
750        // Extend from the ListArray.
751        let source = source
752            .into_array()
753            .execute::<ListViewArray>(&mut ctx)
754            .unwrap();
755        builder
756            .append_listview_array(source.as_view(), &mut ctx)
757            .unwrap();
758
759        // Extend from empty array (should be no-op).
760        let empty_source = ListArray::from_iter_opt_slow::<u32, _, Vec<i32>>(
761            std::iter::empty::<Option<Vec<i32>>>(),
762            Arc::new(I32.into()),
763        )
764        .unwrap();
765        let empty_source = empty_source
766            .into_array()
767            .execute::<ListViewArray>(&mut ctx)
768            .unwrap();
769        builder
770            .append_listview_array(empty_source.as_view(), &mut ctx)
771            .unwrap();
772
773        let listview = builder.finish_into_listview();
774        assert_eq!(listview.len(), 4);
775
776        // Check the extended data.
777        // First list: [0] (initial data).
778        assert_arrays_eq!(
779            listview.list_elements_at(0).unwrap(),
780            PrimitiveArray::from_iter([0i32]),
781            &mut ctx
782        );
783
784        // Second list: [1, 2, 3] (from source).
785        assert_arrays_eq!(
786            listview.list_elements_at(1).unwrap(),
787            PrimitiveArray::from_iter([1i32, 2, 3]),
788            &mut ctx
789        );
790
791        // Third list: null (from source).
792        assert!(
793            !listview
794                .validity()
795                .vortex_expect("listview validity should be derivable")
796                .execute_is_valid(2, &mut ctx)
797                .unwrap()
798        );
799
800        // Fourth list: [4, 5] (from source).
801        assert_arrays_eq!(
802            listview.list_elements_at(3).unwrap(),
803            PrimitiveArray::from_iter([4i32, 5]),
804            &mut ctx
805        );
806    }
807
808    #[test]
809    fn test_append_list_array_grows_builder() -> VortexResult<()> {
810        let mut ctx = array_session().create_execution_ctx();
811        let dtype: Arc<DType> = Arc::new(I32.into());
812
813        // Enough lists to exceed the offsets/sizes capacity of a zero-capacity builder, so
814        // appending must grow the builder rather than panic in `uninit_range`.
815        let lists: Vec<Option<Vec<i32>>> =
816            (0..100).map(|i| (i % 10 != 0).then(|| vec![i])).collect();
817        let source = ListArray::from_iter_opt_slow::<u32, _, _>(lists.clone(), Arc::clone(&dtype))?;
818
819        let mut builder =
820            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
821        builder.append_list_array(source.as_view(), &mut ctx)?;
822        // Append a second time to check growth from a non-empty builder and offset rebasing.
823        builder.append_list_array(source.as_view(), &mut ctx)?;
824
825        let listview = builder.finish_into_listview();
826        assert!(listview.is_zero_copy_to_list());
827
828        let expected = ListArray::from_iter_opt_slow::<u32, _, _>(
829            lists.iter().cloned().chain(lists.iter().cloned()),
830            dtype,
831        )?;
832        assert_arrays_eq!(listview, expected, &mut ctx);
833
834        Ok(())
835    }
836
837    #[test]
838    fn test_extend_from_array_overlapping_listview() {
839        let mut ctx = array_session().create_execution_ctx();
840        let dtype: Arc<DType> = Arc::new(I32.into());
841
842        // Non-ZCTL source:
843        // - List 0: [10, 20]
844        // - List 1: null (size is intentionally non-zero in source metadata)
845        // - List 2: [10]
846        let source = unsafe {
847            ListViewArray::new_unchecked(
848                buffer![10i32, 20, 30].into_array(),
849                buffer![0u32, 1, 0].into_array(),
850                buffer![2u8, 2, 1].into_array(),
851                Validity::from_iter([true, false, true]),
852            )
853        };
854        assert!(!source.is_zero_copy_to_list());
855
856        let mut builder =
857            ListViewBuilder::<u32, u8>::with_capacity(Arc::clone(&dtype), Nullable, 0, 0);
858        builder
859            .append_listview_array(source.as_view(), &mut ctx)
860            .unwrap();
861
862        let listview = builder.finish_into_listview();
863        assert_eq!(listview.len(), 3);
864        assert!(listview.is_zero_copy_to_list());
865
866        assert_arrays_eq!(
867            listview.list_elements_at(0).unwrap(),
868            PrimitiveArray::from_iter([10i32, 20]),
869            &mut ctx
870        );
871        assert!(
872            !listview
873                .validity()
874                .vortex_expect("listview validity should be derivable")
875                .execute_is_valid(1, &mut ctx)
876                .unwrap()
877        );
878        assert_eq!(listview.list_elements_at(1).unwrap().len(), 0);
879        assert_arrays_eq!(
880            listview.list_elements_at(2).unwrap(),
881            PrimitiveArray::from_iter([10i32]),
882            &mut ctx
883        );
884    }
885
886    #[test]
887    fn test_error_append_null_to_non_nullable() {
888        let dtype: Arc<DType> = Arc::new(I32.into());
889        let mut builder =
890            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), NonNullable, 0, 0);
891
892        // Create a null list with nullable type (since Scalar::null requires nullable type).
893        let null_scalar = Scalar::null(DType::List(dtype, Nullable));
894        let null_list = null_scalar.as_list();
895
896        // This should fail because we're trying to append a null to a non-nullable builder.
897        let result = builder.append_value(null_list);
898        assert!(result.is_err());
899        assert!(
900            result
901                .unwrap_err()
902                .to_string()
903                .contains("null value to non-nullable")
904        );
905    }
906
907    #[test]
908    fn test_append_array_as_list() {
909        let dtype: Arc<DType> = Arc::new(I32.into());
910        let mut ctx = array_session().create_execution_ctx();
911        let mut builder =
912            ListViewBuilder::<u32, u32>::with_capacity(Arc::clone(&dtype), NonNullable, 20, 10);
913
914        // Append a primitive array as a single list entry.
915        let arr1 = buffer![1i32, 2, 3].into_array();
916        builder.append_array_as_list(&arr1, &mut ctx).unwrap();
917
918        // Interleave with a list scalar.
919        builder
920            .append_value(
921                Scalar::list(
922                    Arc::clone(&dtype),
923                    vec![10i32.into(), 11i32.into()],
924                    NonNullable,
925                )
926                .as_list(),
927            )
928            .unwrap();
929
930        // Append another primitive array as a single list entry.
931        let arr2 = buffer![4i32, 5].into_array();
932        builder.append_array_as_list(&arr2, &mut ctx).unwrap();
933
934        // Append an empty array as a single list entry (empty list).
935        let arr3 = buffer![0i32; 0].into_array();
936        builder.append_array_as_list(&arr3, &mut ctx).unwrap();
937
938        // Interleave with another list scalar.
939        builder
940            .append_value(Scalar::list_empty(Arc::clone(&dtype), NonNullable).as_list())
941            .unwrap();
942
943        let listview = builder.finish_into_listview();
944        assert_eq!(listview.len(), 5);
945
946        // Verify elements array: [1, 2, 3, 10, 11, 4, 5].
947        assert_arrays_eq!(
948            listview.elements(),
949            PrimitiveArray::from_iter([1i32, 2, 3, 10, 11, 4, 5]),
950            &mut ctx
951        );
952
953        // Verify offsets array.
954        assert_arrays_eq!(
955            listview.offsets(),
956            PrimitiveArray::from_iter([0u32, 3, 5, 7, 7]),
957            &mut ctx
958        );
959
960        // Verify sizes array.
961        assert_arrays_eq!(
962            listview.sizes(),
963            PrimitiveArray::from_iter([3u32, 2, 2, 0, 0]),
964            &mut ctx
965        );
966
967        // Test dtype mismatch error.
968        let mut builder = ListViewBuilder::<u32, u32>::with_capacity(dtype, NonNullable, 20, 10);
969        let wrong_dtype_arr = buffer![1i64, 2, 3].into_array();
970        assert!(
971            builder
972                .append_array_as_list(&wrong_dtype_arr, &mut ctx)
973                .is_err()
974        );
975    }
976}