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