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