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