Skip to main content

vortex_array/arrays/listview/
conversion.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use vortex_error::VortexExpect;
5use vortex_error::VortexResult;
6
7use crate::ArrayRef;
8use crate::Canonical;
9use crate::ExecutionCtx;
10use crate::IntoArray;
11#[expect(deprecated)]
12use crate::ToCanonical as _;
13use crate::arrays::ExtensionArray;
14use crate::arrays::FixedSizeListArray;
15use crate::arrays::ListArray;
16use crate::arrays::ListViewArray;
17use crate::arrays::PrimitiveArray;
18use crate::arrays::StructArray;
19use crate::arrays::extension::ExtensionArrayExt;
20use crate::arrays::fixed_size_list::FixedSizeListArrayExt;
21use crate::arrays::list::ListArrayExt;
22use crate::arrays::listview::ListViewArrayExt;
23use crate::arrays::listview::ListViewRebuildMode;
24use crate::arrays::struct_::StructArrayExt;
25use crate::builders::PrimitiveBuilder;
26use crate::dtype::IntegerPType;
27use crate::dtype::Nullability;
28use crate::match_each_integer_ptype;
29
30/// Creates a `ListViewArray` from a `ListArray` by computing `sizes` from `offsets`.
31///
32/// The output `ListViewArray` will be zero-copyable back to a `ListArray`, and additionally it
33/// will not have any leading or trailing garbage data.
34pub fn list_view_from_list(list: ListArray, ctx: &mut ExecutionCtx) -> VortexResult<ListViewArray> {
35    // If the list is empty, create an empty `ListViewArray` with the same offset `DType` as the
36    // input.
37    if list.is_empty() {
38        return Ok(Canonical::empty(list.dtype()).into_listview());
39    }
40
41    // We reset the offsets here because mostly for convenience, and also because callers of this
42    // function might not expect the output `ListViewArray` to have a bunch of leading and trailing
43    // garbage data when they turn it back into a `ListArray`.
44    let list = list.reset_offsets(false).vortex_expect("This can't fail");
45
46    let list_offsets = list.offsets().clone();
47
48    // Create `sizes` array by computing differences between consecutive offsets.
49    // We use the same `DType` for the sizes as the `offsets` array to ensure compatibility.
50    let sizes = match_each_integer_ptype!(list_offsets.dtype().as_ptype(), |O| {
51        build_sizes_from_offsets::<O>(&list, ctx)?
52    });
53
54    // We need to slice the `offsets` to remove the last element (`ListArray` has `n + 1` offsets).
55    debug_assert_eq!(list_offsets.len(), list.len() + 1);
56    let adjusted_offsets = list_offsets.slice(0..list.len())?;
57
58    // SAFETY: Since everything came from an existing valid `ListArray`, and the `sizes` were
59    // derived from valid and in-order `offsets`, we know these fields are valid.
60    // We also just came directly from a `ListArray`, so we know this is zero-copyable.
61    Ok(unsafe {
62        ListViewArray::new_unchecked(
63            list.elements().clone(),
64            adjusted_offsets,
65            sizes,
66            list.validity()?,
67        )
68        .with_zero_copy_to_list(true)
69    })
70}
71
72/// Builds a sizes array from a `ListArray` by computing differences between consecutive offsets.
73fn build_sizes_from_offsets<O: IntegerPType>(
74    list: &ListArray,
75    ctx: &mut ExecutionCtx,
76) -> VortexResult<ArrayRef> {
77    let len = list.len();
78    let mut sizes_builder = PrimitiveBuilder::<O>::with_capacity(Nullability::NonNullable, len);
79
80    // Create `UninitRange` for direct memory access.
81    let mut sizes_range = sizes_builder.uninit_range(len);
82
83    let offsets = list.offsets().clone().execute::<PrimitiveArray>(ctx)?;
84    let offsets_slice = offsets.as_slice::<O>();
85    debug_assert_eq!(len + 1, offsets_slice.len());
86    debug_assert!(offsets_slice.is_sorted());
87
88    // Compute sizes as the difference between consecutive offsets.
89    for i in 0..len {
90        let size = offsets_slice[i + 1] - offsets_slice[i];
91        sizes_range.set_value(i, size);
92    }
93
94    // SAFETY: We have initialized all values in the range.
95    unsafe {
96        sizes_range.finish();
97    }
98
99    Ok(sizes_builder.finish_into_primitive().into_array())
100}
101
102// TODO(connor)[ListView]: Note that it is not exactly zero-copy because we have to add a single
103// offset at the end, but it is fast enough.
104/// Creates a `ListArray` from a `ListViewArray`. The resulting `ListArray` will not have any
105/// leading or trailing garbage data.
106///
107/// If `ListViewArray::is_zero_copy_to_list` is `true`, then this operation is fast
108///
109/// Otherwise, this function fall back to the (very) expensive path and will rebuild the
110/// `ListArray` from scratch.
111pub fn list_from_list_view(list_view: ListViewArray) -> VortexResult<ListArray> {
112    // Rebuild as zero-copyable to list array and also trim all leading and trailing elements.
113    let zctl_array = list_view.rebuild(ListViewRebuildMode::MakeExact)?;
114    debug_assert!(zctl_array.is_zero_copy_to_list());
115
116    let list_offsets = match_each_integer_ptype!(zctl_array.offsets().dtype().as_ptype(), |O| {
117        // SAFETY: We just made the array zero-copyable to `ListArray`, so the safety contract is
118        // upheld.
119        unsafe { build_list_offsets_from_list_view::<O>(&zctl_array) }
120    });
121
122    // SAFETY: Because the shape of the `ListViewArray` is zero-copyable to a `ListArray`, we
123    // can simply reuse all of the data (besides the offsets). We also trim all of the elements to
124    // make it easier for the caller to use the `ListArray`.
125    Ok(unsafe {
126        ListArray::new_unchecked(
127            zctl_array.elements().clone(),
128            list_offsets,
129            zctl_array.validity()?,
130        )
131    })
132}
133
134// TODO(connor)[ListView]: We can optimize this by always keeping extra memory in `ListViewArray`
135// offsets for an `n+1`th offset.
136/// Builds a `ListArray` offsets array from a `ListViewArray` by constructing `n+1` offsets.
137/// The last offset is computed as `last_offset + last_size`.
138///
139/// # Safety
140///
141/// The `ListViewArray` must have offsets that are sorted, and every size must be equal to the gap
142/// between `offset[i]` and `offset[i + 1]`.
143unsafe fn build_list_offsets_from_list_view<O: IntegerPType>(
144    list_view: &ListViewArray,
145) -> ArrayRef {
146    let len = list_view.len();
147    let mut offsets_builder =
148        PrimitiveBuilder::<O>::with_capacity(Nullability::NonNullable, len + 1);
149
150    // Create uninit range for direct memory access.
151    let mut offsets_range = offsets_builder.uninit_range(len + 1);
152
153    #[expect(deprecated)]
154    let offsets = list_view.offsets().to_primitive();
155    let offsets_slice = offsets.as_slice::<O>();
156    debug_assert!(offsets_slice.is_sorted());
157
158    // Copy the existing n offsets.
159    offsets_range.copy_from_slice(0, offsets_slice);
160
161    // Append the final offset (last offset + last size).
162    let final_offset = if len != 0 {
163        let last_offset = offsets_slice[len - 1];
164
165        let last_size = list_view.size_at(len - 1);
166        let last_size =
167            O::from_usize(last_size).vortex_expect("size somehow did not fit into offsets");
168
169        last_offset + last_size
170    } else {
171        O::zero()
172    };
173
174    offsets_range.set_value(len, final_offset);
175
176    // SAFETY: We have initialized all values in the range.
177    unsafe {
178        offsets_range.finish();
179    }
180
181    offsets_builder.finish_into_primitive().into_array()
182}
183
184/// Recursively converts all `ListViewArray`s to `ListArray`s in a nested array structure.
185///
186/// The conversion happens bottom-up, processing children before parents.
187pub fn recursive_list_from_list_view(array: ArrayRef) -> VortexResult<ArrayRef> {
188    if !array.dtype().is_nested() {
189        return Ok(array);
190    }
191
192    #[expect(deprecated)]
193    let canonical = array.to_canonical()?;
194
195    Ok(match canonical {
196        Canonical::List(listview) => {
197            let converted_elements = recursive_list_from_list_view(listview.elements().clone())?;
198            debug_assert_eq!(converted_elements.len(), listview.elements().len());
199
200            // Avoid cloning if elements didn't change.
201            let listview_with_converted_elements =
202                if !ArrayRef::ptr_eq(&converted_elements, listview.elements()) {
203                    // SAFETY: We are effectively just replacing the child elements array, which
204                    // must have the same length, so all invariants are maintained.
205                    unsafe {
206                        ListViewArray::new_unchecked(
207                            converted_elements,
208                            listview.offsets().clone(),
209                            listview.sizes().clone(),
210                            listview.validity()?,
211                        )
212                        .with_zero_copy_to_list(listview.is_zero_copy_to_list())
213                    }
214                } else {
215                    listview
216                };
217
218            // Make the conversion to `ListArray`.
219            let list_array = list_from_list_view(listview_with_converted_elements)?;
220            list_array.into_array()
221        }
222        Canonical::FixedSizeList(fixed_size_list) => {
223            let converted_elements =
224                recursive_list_from_list_view(fixed_size_list.elements().clone())?;
225
226            // Avoid cloning if elements didn't change.
227            if !ArrayRef::ptr_eq(&converted_elements, fixed_size_list.elements()) {
228                FixedSizeListArray::try_new(
229                    converted_elements,
230                    fixed_size_list.list_size(),
231                    fixed_size_list.validity()?,
232                    fixed_size_list.len(),
233                )
234                .vortex_expect(
235                    "FixedSizeListArray reconstruction should not fail with valid components",
236                )
237                .into_array()
238            } else {
239                fixed_size_list.into_array()
240            }
241        }
242        Canonical::Struct(struct_array) => {
243            let fields = struct_array.unmasked_fields();
244            let mut converted_fields = Vec::with_capacity(fields.len());
245            let mut any_changed = false;
246
247            for field in fields.iter() {
248                let converted_field = recursive_list_from_list_view(field.clone())?;
249                // Avoid cloning if elements didn't change.
250                any_changed |= !ArrayRef::ptr_eq(&converted_field, field);
251                converted_fields.push(converted_field);
252            }
253
254            if any_changed {
255                StructArray::try_new(
256                    struct_array.names().clone(),
257                    converted_fields,
258                    struct_array.len(),
259                    struct_array.validity()?,
260                )
261                .vortex_expect("StructArray reconstruction should not fail with valid components")
262                .into_array()
263            } else {
264                struct_array.into_array()
265            }
266        }
267        Canonical::Extension(ext_array) => {
268            let converted_storage =
269                recursive_list_from_list_view(ext_array.storage_array().clone())?;
270
271            // Avoid cloning if elements didn't change.
272            if !ArrayRef::ptr_eq(&converted_storage, ext_array.storage_array()) {
273                ExtensionArray::new(ext_array.ext_dtype().clone(), converted_storage).into_array()
274            } else {
275                ext_array.into_array()
276            }
277        }
278        _ => unreachable!(),
279    })
280}
281
282#[cfg(test)]
283mod tests {
284
285    use vortex_buffer::buffer;
286    use vortex_error::VortexExpect;
287    use vortex_error::VortexResult;
288
289    use super::super::tests::common::create_basic_listview;
290    use super::super::tests::common::create_empty_lists_listview;
291    use super::super::tests::common::create_nullable_listview;
292    use super::super::tests::common::create_overlapping_listview;
293    use super::recursive_list_from_list_view;
294    use crate::ArrayEq;
295    use crate::ArrayRef;
296    use crate::IntoArray;
297    use crate::LEGACY_SESSION;
298    use crate::Precision;
299    use crate::VortexSessionExecute;
300    use crate::arrays::BoolArray;
301    use crate::arrays::FixedSizeListArray;
302    use crate::arrays::ListArray;
303    use crate::arrays::ListViewArray;
304    use crate::arrays::PrimitiveArray;
305    use crate::arrays::StructArray;
306    use crate::arrays::VarBinViewArray;
307    use crate::arrays::list::ListArrayExt;
308    use crate::arrays::listview::ListViewArrayExt;
309    use crate::arrays::listview::list_from_list_view;
310    use crate::arrays::listview::list_view_from_list;
311    use crate::assert_arrays_eq;
312    use crate::dtype::FieldNames;
313    use crate::validity::Validity;
314
315    #[test]
316    fn test_list_to_listview_basic() -> VortexResult<()> {
317        // Create a basic ListArray: [[0,1,2], [3,4], [5,6], [7,8,9]].
318        let elements = buffer![0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_array();
319        let offsets = buffer![0u32, 3, 5, 7, 10].into_array();
320        let list_array = ListArray::try_new(elements.clone(), offsets, Validity::NonNullable)?;
321
322        let mut ctx = LEGACY_SESSION.create_execution_ctx();
323        let list_view = list_view_from_list(list_array.clone(), &mut ctx)?;
324
325        // Verify structure.
326        assert_eq!(list_view.len(), 4);
327        assert_arrays_eq!(elements, list_view.elements().clone());
328
329        // Verify offsets (should be same but without last element).
330        let expected_offsets = buffer![0u32, 3, 5, 7].into_array();
331        assert_arrays_eq!(expected_offsets, list_view.offsets().clone());
332
333        // Verify sizes.
334        let expected_sizes = buffer![3u32, 2, 2, 3].into_array();
335        assert_arrays_eq!(expected_sizes, list_view.sizes().clone());
336
337        // Verify data integrity.
338        assert_arrays_eq!(list_array, list_view);
339        Ok(())
340    }
341
342    #[test]
343    fn test_listview_to_list_zero_copy() -> VortexResult<()> {
344        let list_view = create_basic_listview();
345        let list_array = list_from_list_view(list_view.clone())?;
346
347        // Should have same elements.
348        assert_arrays_eq!(list_view.elements().clone(), list_array.elements().clone());
349
350        // ListArray offsets should have n+1 elements for n lists (add the final offset).
351        // Check that the first n offsets match.
352        let list_array_offsets_without_last = list_array.offsets().slice(0..list_view.len())?;
353        assert_arrays_eq!(list_view.offsets().clone(), list_array_offsets_without_last);
354
355        // Verify data integrity.
356        assert_arrays_eq!(list_view, list_array);
357        Ok(())
358    }
359
360    #[test]
361    fn test_empty_array_conversions() -> VortexResult<()> {
362        // Empty ListArray to ListViewArray.
363        let empty_elements = PrimitiveArray::from_iter::<[i32; 0]>([]).into_array();
364        let empty_offsets = buffer![0u32].into_array();
365        let empty_list = ListArray::try_new(empty_elements, empty_offsets, Validity::NonNullable)?;
366
367        // This conversion will create an empty ListViewArray.
368        // Note: list_view_from_list handles the empty case specially.
369        let mut ctx = LEGACY_SESSION.create_execution_ctx();
370        let empty_list_view = list_view_from_list(empty_list.clone(), &mut ctx)?;
371        assert_eq!(empty_list_view.len(), 0);
372
373        // Convert back.
374        let converted_back = list_from_list_view(empty_list_view)?;
375        assert_eq!(converted_back.len(), 0);
376        // For empty arrays, we can't use assert_arrays_eq directly since the offsets might differ.
377        // Just check that it's empty.
378        assert_eq!(empty_list.len(), converted_back.len());
379        Ok(())
380    }
381
382    #[test]
383    fn test_nullable_conversions() -> VortexResult<()> {
384        // Create nullable ListArray: [[10,20], null, [50]].
385        let elements = buffer![10i32, 20, 30, 40, 50].into_array();
386        let offsets = buffer![0u32, 2, 4, 5].into_array();
387        let validity = Validity::Array(BoolArray::from_iter(vec![true, false, true]).into_array());
388        let nullable_list = ListArray::try_new(elements, offsets, validity.clone())?;
389
390        let mut ctx = LEGACY_SESSION.create_execution_ctx();
391        let nullable_list_view = list_view_from_list(nullable_list.clone(), &mut ctx)?;
392
393        // Verify validity is preserved.
394        assert!(
395            nullable_list_view
396                .validity()
397                .vortex_expect("listview validity should be derivable")
398                .array_eq(&validity, Precision::Ptr)
399        );
400        assert_eq!(nullable_list_view.len(), 3);
401
402        // Round-trip conversion.
403        let converted_back = list_from_list_view(nullable_list_view)?;
404        assert_arrays_eq!(nullable_list, converted_back);
405        Ok(())
406    }
407
408    #[test]
409    fn test_non_zero_copy_listview_to_list() -> VortexResult<()> {
410        // Create ListViewArray with overlapping lists (not zero-copyable).
411        let list_view = create_overlapping_listview();
412        let list_array = list_from_list_view(list_view.clone())?;
413
414        // The resulting ListArray should have monotonic offsets.
415        for i in 0..list_array.len() {
416            let start = list_array.offset_at(i)?;
417            let end = list_array.offset_at(i + 1)?;
418            assert!(end >= start, "Offsets should be monotonic after conversion");
419        }
420
421        // The data should still be correct even though it required a rebuild.
422        assert_arrays_eq!(list_view, list_array);
423        Ok(())
424    }
425
426    #[test]
427    fn test_empty_sublists() -> VortexResult<()> {
428        let empty_lists_view = create_empty_lists_listview();
429
430        // Convert to ListArray.
431        let list_array = list_from_list_view(empty_lists_view.clone())?;
432        assert_eq!(list_array.len(), 4);
433
434        // All sublists should be empty.
435        for i in 0..list_array.len() {
436            assert_eq!(list_array.list_elements_at(i)?.len(), 0);
437        }
438
439        // Round-trip.
440        let mut ctx = LEGACY_SESSION.create_execution_ctx();
441        let converted_back = list_view_from_list(list_array, &mut ctx)?;
442        assert_arrays_eq!(empty_lists_view, converted_back);
443        Ok(())
444    }
445
446    #[test]
447    fn test_different_offset_types() -> VortexResult<()> {
448        // Test with i32 offsets.
449        let elements = buffer![1i32, 2, 3, 4, 5].into_array();
450        let i32_offsets = buffer![0i32, 2, 5].into_array();
451        let list_i32 =
452            ListArray::try_new(elements.clone(), i32_offsets.clone(), Validity::NonNullable)?;
453
454        let mut ctx = LEGACY_SESSION.create_execution_ctx();
455        let list_view_i32 = list_view_from_list(list_i32.clone(), &mut ctx)?;
456        assert_eq!(list_view_i32.offsets().dtype(), i32_offsets.dtype());
457        assert_eq!(list_view_i32.sizes().dtype(), i32_offsets.dtype());
458
459        // Test with i64 offsets.
460        let i64_offsets = buffer![0i64, 2, 5].into_array();
461        let list_i64 = ListArray::try_new(elements, i64_offsets.clone(), Validity::NonNullable)?;
462
463        let list_view_i64 = list_view_from_list(list_i64.clone(), &mut ctx)?;
464        assert_eq!(list_view_i64.offsets().dtype(), i64_offsets.dtype());
465        assert_eq!(list_view_i64.sizes().dtype(), i64_offsets.dtype());
466
467        // Verify data integrity.
468        assert_arrays_eq!(list_i32, list_view_i32);
469        assert_arrays_eq!(list_i64, list_view_i64);
470        Ok(())
471    }
472
473    #[test]
474    fn test_round_trip_conversions() -> VortexResult<()> {
475        let mut ctx = LEGACY_SESSION.create_execution_ctx();
476
477        // Test 1: Basic round-trip.
478        let original = create_basic_listview();
479        let to_list = list_from_list_view(original.clone())?;
480        let back_to_view = list_view_from_list(to_list, &mut ctx)?;
481        assert_arrays_eq!(original, back_to_view);
482
483        // Test 2: Nullable round-trip.
484        let nullable = create_nullable_listview();
485        let nullable_to_list = list_from_list_view(nullable.clone())?;
486        let nullable_back = list_view_from_list(nullable_to_list, &mut ctx)?;
487        assert_arrays_eq!(nullable, nullable_back);
488
489        // Test 3: Non-zero-copyable round-trip.
490        let overlapping = create_overlapping_listview();
491
492        let overlapping_to_list = list_from_list_view(overlapping.clone())?;
493        let overlapping_back = list_view_from_list(overlapping_to_list, &mut ctx)?;
494        assert_arrays_eq!(overlapping, overlapping_back);
495        Ok(())
496    }
497
498    #[test]
499    fn test_single_element_lists() -> VortexResult<()> {
500        // Create lists with single elements: [[100], [200], [300]].
501        let elements = buffer![100i32, 200, 300].into_array();
502        let offsets = buffer![0u32, 1, 2, 3].into_array();
503        let single_elem_list = ListArray::try_new(elements, offsets, Validity::NonNullable)?;
504
505        let mut ctx = LEGACY_SESSION.create_execution_ctx();
506        let list_view = list_view_from_list(single_elem_list.clone(), &mut ctx)?;
507        assert_eq!(list_view.len(), 3);
508
509        // Verify sizes are all 1.
510        let expected_sizes = buffer![1u32, 1, 1].into_array();
511        assert_arrays_eq!(expected_sizes, list_view.sizes().clone());
512
513        // Round-trip.
514        let converted_back = list_from_list_view(list_view)?;
515        assert_arrays_eq!(single_elem_list, converted_back);
516        Ok(())
517    }
518
519    #[test]
520    fn test_mixed_empty_and_non_empty_lists() -> VortexResult<()> {
521        // Create: [[1,2], [], [3], [], [4,5,6]].
522        let elements = buffer![1i32, 2, 3, 4, 5, 6].into_array();
523        let offsets = buffer![0u32, 2, 2, 3, 3, 6].into_array();
524        let mixed_list = ListArray::try_new(elements, offsets, Validity::NonNullable)?;
525
526        let mut ctx = LEGACY_SESSION.create_execution_ctx();
527        let list_view = list_view_from_list(mixed_list.clone(), &mut ctx)?;
528        assert_eq!(list_view.len(), 5);
529
530        // Verify sizes.
531        let expected_sizes = buffer![2u32, 0, 1, 0, 3].into_array();
532        assert_arrays_eq!(expected_sizes, list_view.sizes().clone());
533
534        // Round-trip.
535        let converted_back = list_from_list_view(list_view)?;
536        assert_arrays_eq!(mixed_list, converted_back);
537        Ok(())
538    }
539
540    #[test]
541    fn test_recursive_simple_listview() -> VortexResult<()> {
542        let list_view = create_basic_listview();
543        let result = recursive_list_from_list_view(list_view.clone().into_array())?;
544
545        assert_eq!(result.len(), list_view.len());
546        assert_arrays_eq!(list_view.into_array(), result);
547        Ok(())
548    }
549
550    #[test]
551    fn test_recursive_nested_listview() -> VortexResult<()> {
552        let inner_elements = buffer![1i32, 2, 3].into_array();
553        let inner_offsets = buffer![0u32, 2].into_array();
554        let inner_sizes = buffer![2u32, 1].into_array();
555        let inner_listview = unsafe {
556            ListViewArray::new_unchecked(
557                inner_elements,
558                inner_offsets,
559                inner_sizes,
560                Validity::NonNullable,
561            )
562            .with_zero_copy_to_list(true)
563        };
564
565        let outer_offsets = buffer![0u32, 1].into_array();
566        let outer_sizes = buffer![1u32, 1].into_array();
567        let outer_listview = unsafe {
568            ListViewArray::new_unchecked(
569                inner_listview.into_array(),
570                outer_offsets,
571                outer_sizes,
572                Validity::NonNullable,
573            )
574            .with_zero_copy_to_list(true)
575        };
576
577        let result = recursive_list_from_list_view(outer_listview.clone().into_array())?;
578
579        assert_eq!(result.len(), 2);
580        assert_arrays_eq!(outer_listview.into_array(), result);
581        Ok(())
582    }
583
584    #[test]
585    fn test_recursive_struct_with_listview_fields() -> VortexResult<()> {
586        let listview_field = create_basic_listview().into_array();
587        let primitive_field = buffer![10i32, 20, 30, 40].into_array();
588
589        let struct_array = StructArray::try_new(
590            FieldNames::from(["lists", "values"]),
591            vec![listview_field, primitive_field],
592            4,
593            Validity::NonNullable,
594        )?;
595
596        let result = recursive_list_from_list_view(struct_array.clone().into_array())?;
597
598        assert_eq!(result.len(), 4);
599        assert_arrays_eq!(struct_array.into_array(), result);
600        Ok(())
601    }
602
603    #[test]
604    fn test_recursive_fixed_size_list_with_listview_elements() -> VortexResult<()> {
605        let lv1_elements = buffer![1i32, 2].into_array();
606        let lv1_offsets = buffer![0u32].into_array();
607        let lv1_sizes = buffer![2u32].into_array();
608        let lv1 = unsafe {
609            ListViewArray::new_unchecked(
610                lv1_elements,
611                lv1_offsets,
612                lv1_sizes,
613                Validity::NonNullable,
614            )
615            .with_zero_copy_to_list(true)
616        };
617
618        let lv2_elements = buffer![3i32, 4].into_array();
619        let lv2_offsets = buffer![0u32].into_array();
620        let lv2_sizes = buffer![2u32].into_array();
621        let lv2 = unsafe {
622            ListViewArray::new_unchecked(
623                lv2_elements,
624                lv2_offsets,
625                lv2_sizes,
626                Validity::NonNullable,
627            )
628            .with_zero_copy_to_list(true)
629        };
630
631        let dtype = lv1.dtype().clone();
632        let chunked_listviews =
633            crate::arrays::ChunkedArray::try_new(vec![lv1.into_array(), lv2.into_array()], dtype)?;
634
635        let fixed_list =
636            FixedSizeListArray::new(chunked_listviews.into_array(), 1, Validity::NonNullable, 2);
637
638        let result = recursive_list_from_list_view(fixed_list.clone().into_array())?;
639
640        assert_eq!(result.len(), 2);
641        assert_arrays_eq!(fixed_list.into_array(), result);
642        Ok(())
643    }
644
645    #[test]
646    fn test_recursive_deep_nesting() -> VortexResult<()> {
647        let innermost_elements = buffer![1i32, 2, 3].into_array();
648        let innermost_offsets = buffer![0u32, 2].into_array();
649        let innermost_sizes = buffer![2u32, 1].into_array();
650        let innermost_listview = unsafe {
651            ListViewArray::new_unchecked(
652                innermost_elements,
653                innermost_offsets,
654                innermost_sizes,
655                Validity::NonNullable,
656            )
657            .with_zero_copy_to_list(true)
658        };
659
660        let struct_array = StructArray::try_new(
661            FieldNames::from(["inner_lists"]),
662            vec![innermost_listview.into_array()],
663            2,
664            Validity::NonNullable,
665        )?;
666
667        let outer_offsets = buffer![0u32, 1].into_array();
668        let outer_sizes = buffer![1u32, 1].into_array();
669        let outer_listview = unsafe {
670            ListViewArray::new_unchecked(
671                struct_array.into_array(),
672                outer_offsets,
673                outer_sizes,
674                Validity::NonNullable,
675            )
676            .with_zero_copy_to_list(true)
677        };
678
679        let result = recursive_list_from_list_view(outer_listview.clone().into_array())?;
680
681        assert_eq!(result.len(), 2);
682        assert_arrays_eq!(outer_listview.into_array(), result);
683        Ok(())
684    }
685
686    #[test]
687    fn test_recursive_primitive_unchanged() -> VortexResult<()> {
688        let prim = buffer![1i32, 2, 3].into_array();
689        let prim_clone = prim.clone();
690        let result = recursive_list_from_list_view(prim)?;
691
692        assert!(ArrayRef::ptr_eq(&result, &prim_clone));
693        Ok(())
694    }
695
696    #[test]
697    fn test_recursive_mixed_listview_and_list() -> VortexResult<()> {
698        let listview = create_basic_listview();
699        let list = list_from_list_view(listview.clone())?;
700
701        let struct_array = StructArray::try_new(
702            FieldNames::from(["listview_field", "list_field"]),
703            vec![listview.into_array(), list.into_array()],
704            4,
705            Validity::NonNullable,
706        )?;
707
708        let result = recursive_list_from_list_view(struct_array.clone().into_array())?;
709
710        assert_eq!(result.len(), 4);
711        assert_arrays_eq!(struct_array.into_array(), result);
712        Ok(())
713    }
714
715    /// Regression test for <https://github.com/vortex-data/vortex/issues/6882>.
716    ///
717    /// An empty `ListViewArray` constructed via `try_new` has `is_zero_copy_to_list: false`.
718    /// `list_from_list_view` should still succeed because empty arrays are trivially
719    /// zero-copyable.
720    #[test]
721    fn test_empty_listview_to_list_without_zctl_flag() -> VortexResult<()> {
722        let elements = VarBinViewArray::from_iter_str(Vec::<&str>::new()).into_array();
723        let offsets = PrimitiveArray::from_iter(Vec::<i16>::new()).into_array();
724        let sizes = PrimitiveArray::from_iter(Vec::<i16>::new()).into_array();
725        let list_view = ListViewArray::try_new(elements, offsets, sizes, Validity::AllValid)?;
726
727        // `try_new` sets `is_zero_copy_to_list: false`.
728        assert!(!list_view.is_zero_copy_to_list());
729
730        let list_array = list_from_list_view(list_view)?;
731        assert_eq!(list_array.len(), 0);
732        Ok(())
733    }
734}