vortex_array/arrays/listview/
conversion.rs

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