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