Skip to main content

vortex_array/arrays/list/compute/
take.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use itertools::Itertools as _;
5use vortex_buffer::BufferMut;
6use vortex_error::VortexExpect;
7use vortex_error::VortexResult;
8use vortex_error::vortex_ensure_eq;
9use vortex_error::vortex_err;
10use vortex_mask::Mask;
11
12use crate::ArrayRef;
13use crate::Columnar;
14use crate::IntoArray;
15use crate::array::ArrayView;
16use crate::arrays::ConstantArray;
17use crate::arrays::List;
18use crate::arrays::ListArray;
19use crate::arrays::PiecewiseSequence;
20use crate::arrays::PiecewiseSequenceArray;
21use crate::arrays::Primitive;
22use crate::arrays::PrimitiveArray;
23use crate::arrays::dict::TakeExecute;
24use crate::arrays::list::ListArrayExt;
25use crate::arrays::list::ListArraySlotsExt;
26use crate::arrays::piecewise_sequence::constant_unsigned_usize;
27use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
28use crate::arrays::primitive::PrimitiveArrayExt;
29use crate::dtype::IntegerPType;
30use crate::dtype::UnsignedPType;
31use crate::executor::ExecutionCtx;
32use crate::match_each_unsigned_integer_ptype;
33use crate::match_smallest_offset_type;
34use crate::validity::Validity;
35
36// TODO(connor)[ListView]: Re-revert to the version where we simply convert to a `ListView` and call
37// the `ListView::take` compute function once `ListView` is more stable.
38
39impl TakeExecute for List {
40    /// Take implementation for [`ListArray`].
41    ///
42    /// Unlike `ListView`, `ListArray` must rebuild the elements array to maintain its invariant
43    /// that lists are stored contiguously and in-order (`offset[i+1] >= offset[i]`). Taking
44    /// non-contiguous indices would violate this requirement.
45    #[expect(clippy::cognitive_complexity)]
46    fn take(
47        array: ArrayView<'_, List>,
48        indices: &ArrayRef,
49        ctx: &mut ExecutionCtx,
50    ) -> VortexResult<Option<ArrayRef>> {
51        if let Some(piecewise_indices) = indices.as_opt::<PiecewiseSequence>()
52            && let Some(taken) = take_slices(array, piecewise_indices, indices, ctx)?
53        {
54            return Ok(Some(taken));
55        }
56
57        let new_validity = array.validity()?.take(indices)?;
58        let indices = indices.clone().execute::<PrimitiveArray>(ctx)?;
59        let indices = indices.reinterpret_cast(indices.ptype().to_unsigned());
60        let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
61        let offsets = offsets.reinterpret_cast(offsets.ptype().to_unsigned());
62        let validity_mask = new_validity.execute_mask(indices.len(), ctx)?;
63        // This is an over-approximation of the total number of elements in the resulting array.
64        let total_approx = array.elements().len().saturating_mul(indices.len());
65
66        match_each_unsigned_integer_ptype!(offsets.ptype(), |O| {
67            match_each_unsigned_integer_ptype!(indices.ptype(), |I| {
68                match_smallest_offset_type!(total_approx, |OutOffset| {
69                    take_with_piecewise_elements::<I, O, OutOffset>(
70                        array,
71                        offsets.as_view(),
72                        indices.as_view(),
73                        new_validity,
74                        &validity_mask,
75                    )
76                    .map(Some)
77                })
78            })
79        })
80    }
81}
82
83fn take_with_piecewise_elements<I: IntegerPType, O: IntegerPType, OutOffset: IntegerPType>(
84    array: ArrayView<'_, List>,
85    offsets_array: ArrayView<'_, Primitive>,
86    indices_array: ArrayView<'_, Primitive>,
87    new_validity: Validity,
88    validity_mask: &Mask,
89) -> VortexResult<ArrayRef> {
90    let offsets: &[O] = offsets_array.as_slice();
91    let indices: &[I] = indices_array.as_slice();
92
93    let offsets_capacity = indices
94        .len()
95        .checked_add(1)
96        .ok_or_else(|| vortex_err!("List take offsets length overflow"))?;
97    let mut new_offsets = BufferMut::<OutOffset>::with_capacity(offsets_capacity);
98    let mut element_starts = BufferMut::<u64>::with_capacity(indices.len());
99    let mut element_lengths = BufferMut::<u64>::with_capacity(indices.len());
100
101    let mut current_offset = 0usize;
102    new_offsets.push(OutOffset::zero());
103
104    for (&data_idx, is_valid) in indices.iter().zip_eq(validity_mask.iter()) {
105        if !is_valid {
106            new_offsets.push(new_offset_value::<OutOffset>(current_offset));
107            element_starts.push(0);
108            element_lengths.push(0);
109            continue;
110        }
111
112        let data_idx: usize = data_idx.as_();
113
114        let start = offsets[data_idx];
115        let stop = offsets[data_idx + 1];
116        let start: usize = start.as_();
117        let stop: usize = stop.as_();
118        let length = stop - start;
119
120        current_offset = current_offset
121            .checked_add(length)
122            .ok_or_else(|| vortex_err!("List take output elements length overflow"))?;
123        new_offsets.push(new_offset_value::<OutOffset>(current_offset));
124        element_starts.push(start as u64);
125        element_lengths.push(length as u64);
126    }
127
128    let new_offsets = PrimitiveArray::new(new_offsets.freeze(), Validity::NonNullable).into_array();
129    let multipliers = ConstantArray::new(1u64, element_starts.len()).into_array();
130
131    // SAFETY: valid source rows contribute ranges derived from list offsets; null index/source
132    // rows contribute zero-length placeholder ranges. `current_offset` is the sum of all generated
133    // element lengths, and multiplier 1 preserves contiguous ranges.
134    let element_indices = unsafe {
135        PiecewiseSequenceArray::new_unchecked(
136            element_starts.into_array(),
137            element_lengths.into_array(),
138            multipliers,
139            current_offset,
140        )
141    };
142    let new_elements = array.elements().take(element_indices.into_array())?;
143
144    // SAFETY: offsets are rebuilt from the gathered element ranges and have one entry per output
145    // row plus the leading zero; validity is produced by the usual take-validity path.
146    Ok(unsafe { ListArray::new_unchecked(new_elements, new_offsets, new_validity) }.into_array())
147}
148
149fn take_slices(
150    array: ArrayView<'_, List>,
151    indices: ArrayView<'_, PiecewiseSequence>,
152    indices_ref: &ArrayRef,
153    ctx: &mut ExecutionCtx,
154) -> VortexResult<Option<ArrayRef>> {
155    let Some((starts, lengths)) = maybe_contiguous_slices(indices, ctx)? else {
156        return Ok(None);
157    };
158    let data_validity = array
159        .list_validity()
160        .execute_mask(array.as_ref().len(), ctx)?;
161    let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
162    let offsets = offsets.reinterpret_cast(offsets.ptype().to_unsigned());
163    let output_len = indices_ref.len();
164
165    let taken = match lengths {
166        Columnar::Constant(lengths) => {
167            let length = constant_unsigned_usize(&lengths);
168            take_slices_constant_start_dispatch(
169                array,
170                &starts,
171                length,
172                &offsets,
173                indices_ref,
174                output_len,
175                &data_validity,
176            )?
177        }
178        Columnar::Canonical(lengths) => {
179            let lengths = lengths.into_primitive();
180            take_slices_start_dispatch(
181                array,
182                &starts,
183                &lengths,
184                &offsets,
185                indices_ref,
186                output_len,
187                &data_validity,
188            )?
189        }
190    };
191    Ok(Some(taken))
192}
193
194fn take_slices_constant_start_dispatch(
195    array: ArrayView<'_, List>,
196    starts: &PrimitiveArray,
197    length: usize,
198    offsets: &PrimitiveArray,
199    indices_ref: &ArrayRef,
200    output_len: usize,
201    data_validity: &Mask,
202) -> VortexResult<ArrayRef> {
203    match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
204        take_slices_constant_offset_dispatch::<S>(
205            array,
206            starts,
207            length,
208            offsets,
209            indices_ref,
210            output_len,
211            data_validity,
212        )
213    })
214}
215
216fn take_slices_constant_offset_dispatch<S>(
217    array: ArrayView<'_, List>,
218    starts: &PrimitiveArray,
219    length: usize,
220    offsets: &PrimitiveArray,
221    indices_ref: &ArrayRef,
222    output_len: usize,
223    data_validity: &Mask,
224) -> VortexResult<ArrayRef>
225where
226    S: UnsignedPType,
227{
228    match_each_unsigned_integer_ptype!(offsets.ptype(), |O| {
229        take_slices_constant_length::<S, O>(
230            array,
231            starts.as_slice::<S>(),
232            length,
233            offsets.as_slice::<O>(),
234            indices_ref,
235            output_len,
236            data_validity,
237        )
238    })
239}
240
241fn take_slices_start_dispatch(
242    array: ArrayView<'_, List>,
243    starts: &PrimitiveArray,
244    lengths: &PrimitiveArray,
245    offsets: &PrimitiveArray,
246    indices_ref: &ArrayRef,
247    output_len: usize,
248    data_validity: &Mask,
249) -> VortexResult<ArrayRef> {
250    match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
251        take_slices_length_dispatch::<S>(
252            array,
253            starts,
254            lengths,
255            offsets,
256            indices_ref,
257            output_len,
258            data_validity,
259        )
260    })
261}
262
263fn take_slices_length_dispatch<S>(
264    array: ArrayView<'_, List>,
265    starts: &PrimitiveArray,
266    lengths: &PrimitiveArray,
267    offsets: &PrimitiveArray,
268    indices_ref: &ArrayRef,
269    output_len: usize,
270    data_validity: &Mask,
271) -> VortexResult<ArrayRef>
272where
273    S: UnsignedPType,
274{
275    match_each_unsigned_integer_ptype!(lengths.ptype(), |L| {
276        take_slices_offset_dispatch::<S, L>(
277            array,
278            starts,
279            lengths,
280            offsets,
281            indices_ref,
282            output_len,
283            data_validity,
284        )
285    })
286}
287
288fn take_slices_offset_dispatch<S, L>(
289    array: ArrayView<'_, List>,
290    starts: &PrimitiveArray,
291    lengths: &PrimitiveArray,
292    offsets: &PrimitiveArray,
293    indices_ref: &ArrayRef,
294    output_len: usize,
295    data_validity: &Mask,
296) -> VortexResult<ArrayRef>
297where
298    S: UnsignedPType,
299    L: UnsignedPType,
300{
301    match_each_unsigned_integer_ptype!(offsets.ptype(), |O| {
302        take_slices_typed::<S, L, O>(
303            array,
304            starts.as_slice::<S>(),
305            lengths.as_slice::<L>(),
306            offsets.as_slice::<O>(),
307            indices_ref,
308            output_len,
309            data_validity,
310        )
311    })
312}
313
314fn take_slices_constant_length<S, Offset>(
315    array: ArrayView<'_, List>,
316    starts: &[S],
317    length: usize,
318    offsets: &[Offset],
319    indices_ref: &ArrayRef,
320    output_len: usize,
321    data_validity: &Mask,
322) -> VortexResult<ArrayRef>
323where
324    S: UnsignedPType,
325    Offset: UnsignedPType,
326{
327    let computed_len = starts
328        .len()
329        .checked_mul(length)
330        .ok_or_else(|| vortex_err!("PiecewiseSequenceArray output length overflows usize"))?;
331    vortex_ensure_eq!(
332        computed_len,
333        output_len,
334        "PiecewiseSequenceArray expanded length does not match declared length",
335    );
336    let all_valid = data_validity.all_true();
337    let total_elements = if all_valid {
338        piecewise_list_elements_len_constant(offsets, starts, length)?
339    } else {
340        piecewise_list_elements_len_constant_validity(offsets, starts, length, data_validity)?
341    };
342    let validity = array.validity()?.take(indices_ref)?;
343
344    match_smallest_offset_type!(total_elements, |OutOffset| {
345        let gathered = if all_valid {
346            gather_piecewise_list_constant_length::<S, Offset, OutOffset>(
347                array.elements(),
348                offsets,
349                starts,
350                length,
351                output_len,
352                total_elements,
353            )?
354        } else {
355            gather_piecewise_list_constant_length_validity::<S, Offset, OutOffset>(
356                array.elements(),
357                offsets,
358                starts,
359                length,
360                output_len,
361                total_elements,
362                data_validity,
363            )?
364        };
365
366        // SAFETY: output offsets are rebuilt from valid monotonic source offsets; output elements
367        // are exactly the gathered child ranges referenced by those offsets; validity has one bit
368        // per output row.
369        Ok(
370            unsafe { ListArray::new_unchecked(gathered.elements, gathered.offsets, validity) }
371                .into_array(),
372        )
373    })
374}
375
376fn take_slices_typed<S, L, Offset>(
377    array: ArrayView<'_, List>,
378    starts: &[S],
379    lengths: &[L],
380    offsets: &[Offset],
381    indices_ref: &ArrayRef,
382    output_len: usize,
383    data_validity: &Mask,
384) -> VortexResult<ArrayRef>
385where
386    S: UnsignedPType,
387    L: UnsignedPType,
388    Offset: UnsignedPType,
389{
390    let mut computed_len = 0usize;
391    for &length in lengths {
392        let length: usize = length.as_();
393        computed_len = computed_len
394            .checked_add(length)
395            .ok_or_else(|| vortex_err!("PiecewiseSequenceArray output length overflows usize"))?;
396    }
397    vortex_ensure_eq!(
398        computed_len,
399        output_len,
400        "PiecewiseSequenceArray expanded length does not match declared length",
401    );
402    let all_valid = data_validity.all_true();
403    let total_elements = if all_valid {
404        piecewise_list_elements_len(offsets, starts, lengths)?
405    } else {
406        piecewise_list_elements_len_validity(offsets, starts, lengths, data_validity)?
407    };
408
409    match_smallest_offset_type!(total_elements, |OutOffset| {
410        let gathered = if all_valid {
411            gather_piecewise_list::<S, L, Offset, OutOffset>(
412                array.elements(),
413                offsets,
414                starts,
415                lengths,
416                output_len,
417                total_elements,
418            )?
419        } else {
420            gather_piecewise_list_validity::<S, L, Offset, OutOffset>(
421                array.elements(),
422                offsets,
423                starts,
424                lengths,
425                output_len,
426                total_elements,
427                data_validity,
428            )?
429        };
430        let validity = array.validity()?.take(indices_ref)?;
431
432        // SAFETY: output offsets are rebuilt from valid monotonic source offsets; output elements
433        // are exactly the gathered child ranges referenced by those offsets; validity has one bit
434        // per output row.
435        Ok(
436            unsafe { ListArray::new_unchecked(gathered.elements, gathered.offsets, validity) }
437                .into_array(),
438        )
439    })
440}
441
442struct GatheredList {
443    elements: ArrayRef,
444    offsets: ArrayRef,
445}
446
447struct ValidPieceGather<OutOffset> {
448    new_offsets: BufferMut<OutOffset>,
449    element_starts: BufferMut<u64>,
450    element_lengths: BufferMut<u64>,
451    output_elements: usize,
452}
453
454fn piecewise_list_elements_len_constant<S, Offset>(
455    offsets: &[Offset],
456    starts: &[S],
457    length: usize,
458) -> VortexResult<usize>
459where
460    S: UnsignedPType,
461    Offset: UnsignedPType,
462{
463    if length == 0 {
464        return Ok(0);
465    }
466
467    let mut total = 0usize;
468    for start in starts {
469        let start: usize = start.as_();
470        let offset_range = &offsets[start..][..=length];
471        let element_start: usize = offset_range[0].as_();
472        let element_end: usize = offset_range[length].as_();
473        total = total
474            .checked_add(element_end - element_start)
475            .ok_or_else(|| vortex_err!("List take output elements length overflow"))?;
476    }
477    Ok(total)
478}
479
480fn piecewise_list_elements_len_constant_validity<S, Offset>(
481    offsets: &[Offset],
482    starts: &[S],
483    length: usize,
484    data_validity: &Mask,
485) -> VortexResult<usize>
486where
487    S: UnsignedPType,
488    Offset: UnsignedPType,
489{
490    if length == 0 {
491        return Ok(0);
492    }
493
494    let mut total = 0usize;
495    for start in starts {
496        let start: usize = start.as_();
497        let additional = valid_piece_elements_len(offsets, data_validity, start, length)?;
498        total = total
499            .checked_add(additional)
500            .ok_or_else(|| vortex_err!("List take output elements length overflow"))?;
501    }
502    Ok(total)
503}
504
505fn piecewise_list_elements_len<S, L, Offset>(
506    offsets: &[Offset],
507    starts: &[S],
508    lengths: &[L],
509) -> VortexResult<usize>
510where
511    S: UnsignedPType,
512    L: UnsignedPType,
513    Offset: UnsignedPType,
514{
515    let mut total = 0usize;
516    for (&start, &length) in starts.iter().zip_eq(lengths) {
517        let start: usize = start.as_();
518        let length: usize = length.as_();
519        let offset_range = &offsets[start..][..=length];
520        let element_start: usize = offset_range[0].as_();
521        let element_end: usize = offset_range[length].as_();
522        total = total
523            .checked_add(element_end - element_start)
524            .ok_or_else(|| vortex_err!("List take output elements length overflow"))?;
525    }
526    Ok(total)
527}
528
529fn piecewise_list_elements_len_validity<S, L, Offset>(
530    offsets: &[Offset],
531    starts: &[S],
532    lengths: &[L],
533    data_validity: &Mask,
534) -> VortexResult<usize>
535where
536    S: UnsignedPType,
537    L: UnsignedPType,
538    Offset: UnsignedPType,
539{
540    let mut total = 0usize;
541    for (&start, &length) in starts.iter().zip_eq(lengths) {
542        let start: usize = start.as_();
543        let length: usize = length.as_();
544        let additional = valid_piece_elements_len(offsets, data_validity, start, length)?;
545        total = total
546            .checked_add(additional)
547            .ok_or_else(|| vortex_err!("List take output elements length overflow"))?;
548    }
549    Ok(total)
550}
551
552fn valid_piece_elements_len<Offset>(
553    offsets: &[Offset],
554    data_validity: &Mask,
555    start: usize,
556    length: usize,
557) -> VortexResult<usize>
558where
559    Offset: UnsignedPType,
560{
561    let offset_range = &offsets[start..][..=length];
562    let mut total = 0usize;
563    for (data_idx, window) in (start..).zip(offset_range.windows(2)) {
564        if !data_validity.value(data_idx) {
565            continue;
566        }
567        let element_start: usize = window[0].as_();
568        let element_end: usize = window[1].as_();
569        total = total
570            .checked_add(element_end - element_start)
571            .ok_or_else(|| vortex_err!("List take output elements length overflow"))?;
572    }
573    Ok(total)
574}
575
576fn gather_piecewise_list_constant_length<S, Offset, OutOffset>(
577    elements: &ArrayRef,
578    offsets: &[Offset],
579    starts: &[S],
580    length: usize,
581    output_len: usize,
582    total_elements: usize,
583) -> VortexResult<GatheredList>
584where
585    S: UnsignedPType,
586    Offset: UnsignedPType,
587    OutOffset: IntegerPType,
588{
589    let offsets_capacity = output_len
590        .checked_add(1)
591        .ok_or_else(|| vortex_err!("List take offsets length overflow"))?;
592    let mut new_offsets = BufferMut::<OutOffset>::with_capacity(offsets_capacity);
593    let mut element_starts = BufferMut::<u64>::with_capacity(starts.len());
594    let mut element_lengths = BufferMut::<u64>::with_capacity(starts.len());
595    let mut output_elements = 0usize;
596
597    new_offsets.push(OutOffset::zero());
598    for start in starts {
599        let start: usize = start.as_();
600        if length == 0 {
601            continue;
602        }
603
604        let offset_range = &offsets[start..][..=length];
605        let element_start: usize = offset_range[0].as_();
606        let element_end: usize = offset_range[length].as_();
607        for &offset in &offset_range[1..] {
608            let offset: usize = offset.as_();
609            let relative = offset - element_start;
610            let output_offset = output_elements + relative;
611            new_offsets.push(new_offset_value::<OutOffset>(output_offset));
612        }
613
614        let element_length = element_end - element_start;
615        element_starts.push(element_start as u64);
616        element_lengths.push(element_length as u64);
617        output_elements += element_length;
618    }
619    debug_assert_eq!(output_elements, total_elements);
620
621    let offsets = PrimitiveArray::new(new_offsets.freeze(), Validity::NonNullable).into_array();
622    let multipliers = ConstantArray::new(1u64, element_starts.len()).into_array();
623    // SAFETY: element ranges are derived from validated source list offsets, and total_elements is
624    // the sum of the gathered element range lengths. Multiplier 1 preserves contiguous ranges.
625    let element_indices = unsafe {
626        PiecewiseSequenceArray::new_unchecked(
627            element_starts.into_array(),
628            element_lengths.into_array(),
629            multipliers,
630            total_elements,
631        )
632    };
633    let elements = elements.take(element_indices.into_array())?;
634
635    Ok(GatheredList { elements, offsets })
636}
637
638fn gather_piecewise_list_constant_length_validity<S, Offset, OutOffset>(
639    elements: &ArrayRef,
640    offsets: &[Offset],
641    starts: &[S],
642    length: usize,
643    output_len: usize,
644    total_elements: usize,
645    data_validity: &Mask,
646) -> VortexResult<GatheredList>
647where
648    S: UnsignedPType,
649    Offset: UnsignedPType,
650    OutOffset: IntegerPType,
651{
652    let offsets_capacity = output_len
653        .checked_add(1)
654        .ok_or_else(|| vortex_err!("List take offsets length overflow"))?;
655    let mut gather = ValidPieceGather {
656        new_offsets: BufferMut::<OutOffset>::with_capacity(offsets_capacity),
657        element_starts: BufferMut::<u64>::with_capacity(output_len),
658        element_lengths: BufferMut::<u64>::with_capacity(output_len),
659        output_elements: 0,
660    };
661
662    gather.new_offsets.push(OutOffset::zero());
663    for start in starts {
664        let start: usize = start.as_();
665        if length == 0 {
666            continue;
667        }
668
669        gather_valid_piece(offsets, data_validity, start, length, &mut gather);
670    }
671    debug_assert_eq!(gather.output_elements, total_elements);
672
673    let offsets =
674        PrimitiveArray::new(gather.new_offsets.freeze(), Validity::NonNullable).into_array();
675    let multipliers = ConstantArray::new(1u64, gather.element_starts.len()).into_array();
676    // SAFETY: element ranges come only from valid source list rows. Source list construction
677    // validated those row offsets, and null source rows produce no element range.
678    let element_indices = unsafe {
679        PiecewiseSequenceArray::new_unchecked(
680            gather.element_starts.into_array(),
681            gather.element_lengths.into_array(),
682            multipliers,
683            total_elements,
684        )
685    };
686    let elements = elements.take(element_indices.into_array())?;
687
688    Ok(GatheredList { elements, offsets })
689}
690
691fn gather_piecewise_list<S, L, Offset, OutOffset>(
692    elements: &ArrayRef,
693    offsets: &[Offset],
694    starts: &[S],
695    lengths: &[L],
696    output_len: usize,
697    total_elements: usize,
698) -> VortexResult<GatheredList>
699where
700    S: UnsignedPType,
701    L: UnsignedPType,
702    Offset: UnsignedPType,
703    OutOffset: IntegerPType,
704{
705    let offsets_capacity = output_len
706        .checked_add(1)
707        .ok_or_else(|| vortex_err!("List take offsets length overflow"))?;
708    let mut new_offsets = BufferMut::<OutOffset>::with_capacity(offsets_capacity);
709    let mut element_starts = BufferMut::<u64>::with_capacity(starts.len());
710    let mut element_lengths = BufferMut::<u64>::with_capacity(lengths.len());
711    let mut output_elements = 0usize;
712
713    new_offsets.push(OutOffset::zero());
714    for (&start, &length) in starts.iter().zip_eq(lengths) {
715        let start: usize = start.as_();
716        let length: usize = length.as_();
717        if length == 0 {
718            continue;
719        }
720
721        let offset_range = &offsets[start..][..=length];
722        let element_start: usize = offset_range[0].as_();
723        let element_end: usize = offset_range[length].as_();
724        for &offset in &offset_range[1..] {
725            let offset: usize = offset.as_();
726            let relative = offset - element_start;
727            let output_offset = output_elements + relative;
728            new_offsets.push(new_offset_value::<OutOffset>(output_offset));
729        }
730
731        let element_length = element_end - element_start;
732        element_starts.push(element_start as u64);
733        element_lengths.push(element_length as u64);
734        output_elements += element_length;
735    }
736    debug_assert_eq!(output_elements, total_elements);
737
738    let offsets = PrimitiveArray::new(new_offsets.freeze(), Validity::NonNullable).into_array();
739    let multipliers = ConstantArray::new(1u64, element_starts.len()).into_array();
740    // SAFETY: element ranges are derived from validated source list offsets, and total_elements is
741    // the sum of the gathered element range lengths. Multiplier 1 preserves contiguous ranges.
742    let element_indices = unsafe {
743        PiecewiseSequenceArray::new_unchecked(
744            element_starts.into_array(),
745            element_lengths.into_array(),
746            multipliers,
747            total_elements,
748        )
749    };
750    let elements = elements.take(element_indices.into_array())?;
751
752    Ok(GatheredList { elements, offsets })
753}
754
755fn gather_piecewise_list_validity<S, L, Offset, OutOffset>(
756    elements: &ArrayRef,
757    offsets: &[Offset],
758    starts: &[S],
759    lengths: &[L],
760    output_len: usize,
761    total_elements: usize,
762    data_validity: &Mask,
763) -> VortexResult<GatheredList>
764where
765    S: UnsignedPType,
766    L: UnsignedPType,
767    Offset: UnsignedPType,
768    OutOffset: IntegerPType,
769{
770    let offsets_capacity = output_len
771        .checked_add(1)
772        .ok_or_else(|| vortex_err!("List take offsets length overflow"))?;
773    let mut gather = ValidPieceGather {
774        new_offsets: BufferMut::<OutOffset>::with_capacity(offsets_capacity),
775        element_starts: BufferMut::<u64>::with_capacity(output_len),
776        element_lengths: BufferMut::<u64>::with_capacity(output_len),
777        output_elements: 0,
778    };
779
780    gather.new_offsets.push(OutOffset::zero());
781    for (&start, &length) in starts.iter().zip_eq(lengths) {
782        let start: usize = start.as_();
783        let length: usize = length.as_();
784        if length == 0 {
785            continue;
786        }
787
788        gather_valid_piece(offsets, data_validity, start, length, &mut gather);
789    }
790    debug_assert_eq!(gather.output_elements, total_elements);
791
792    let offsets =
793        PrimitiveArray::new(gather.new_offsets.freeze(), Validity::NonNullable).into_array();
794    let multipliers = ConstantArray::new(1u64, gather.element_starts.len()).into_array();
795    // SAFETY: element ranges come only from valid source list rows. Source list construction
796    // validated those row offsets, and null source rows produce no element range.
797    let element_indices = unsafe {
798        PiecewiseSequenceArray::new_unchecked(
799            gather.element_starts.into_array(),
800            gather.element_lengths.into_array(),
801            multipliers,
802            total_elements,
803        )
804    };
805    let elements = elements.take(element_indices.into_array())?;
806
807    Ok(GatheredList { elements, offsets })
808}
809
810fn gather_valid_piece<Offset, OutOffset>(
811    offsets: &[Offset],
812    data_validity: &Mask,
813    start: usize,
814    length: usize,
815    gather: &mut ValidPieceGather<OutOffset>,
816) where
817    Offset: UnsignedPType,
818    OutOffset: IntegerPType,
819{
820    let offset_range = &offsets[start..][..=length];
821    for (data_idx, window) in (start..).zip(offset_range.windows(2)) {
822        if !data_validity.value(data_idx) {
823            gather
824                .new_offsets
825                .push(new_offset_value::<OutOffset>(gather.output_elements));
826            continue;
827        }
828
829        let element_start: usize = window[0].as_();
830        let element_end: usize = window[1].as_();
831        let element_length = element_end - element_start;
832        if element_length != 0 {
833            gather.element_starts.push(element_start as u64);
834            gather.element_lengths.push(element_length as u64);
835            gather.output_elements += element_length;
836        }
837        gather
838            .new_offsets
839            .push(new_offset_value::<OutOffset>(gather.output_elements));
840    }
841}
842
843fn new_offset_value<T: IntegerPType>(value: usize) -> T {
844    T::from_usize(value).vortex_expect("output offset fits selected offset type")
845}
846
847#[cfg(test)]
848mod test {
849    use std::sync::Arc;
850
851    use rstest::rstest;
852    use vortex_buffer::buffer;
853    use vortex_error::VortexResult;
854
855    use crate::IntoArray as _;
856    use crate::VortexSessionExecute;
857    use crate::array_session;
858    use crate::arrays::BoolArray;
859    use crate::arrays::ConstantArray;
860    use crate::arrays::ListArray;
861    use crate::arrays::ListViewArray;
862    use crate::arrays::PiecewiseSequenceArray;
863    use crate::arrays::PrimitiveArray;
864    use crate::arrays::listview::ListViewArrayExt;
865    use crate::assert_arrays_eq;
866    use crate::compute::conformance::take::test_take_conformance;
867    use crate::dtype::DType;
868    use crate::dtype::Nullability;
869    use crate::dtype::PType::I32;
870    use crate::scalar::Scalar;
871    use crate::validity::Validity;
872
873    #[test]
874    fn nullable_take() {
875        let mut ctx = array_session().create_execution_ctx();
876        let list = ListArray::try_new(
877            buffer![0i32, 5, 3, 4].into_array(),
878            buffer![0, 2, 3, 4, 4].into_array(),
879            Validity::Array(BoolArray::from_iter(vec![true, true, false, true]).into_array()),
880        )
881        .unwrap()
882        .into_array();
883
884        let idx =
885            PrimitiveArray::from_option_iter(vec![Some(0), None, Some(1), Some(3)]).into_array();
886
887        let result = list.take(idx).unwrap();
888
889        assert_eq!(
890            result.dtype(),
891            &DType::List(
892                Arc::new(DType::Primitive(I32, Nullability::NonNullable)),
893                Nullability::Nullable
894            )
895        );
896
897        let result = result.execute::<ListViewArray>(&mut ctx).unwrap();
898
899        assert_eq!(result.len(), 4);
900
901        let element_dtype: Arc<DType> = Arc::new(I32.into());
902
903        assert!(
904            result
905                .is_valid(0, &mut array_session().create_execution_ctx())
906                .unwrap()
907        );
908        assert_eq!(
909            result
910                .execute_scalar(0, &mut array_session().create_execution_ctx())
911                .unwrap(),
912            Scalar::list(
913                Arc::clone(&element_dtype),
914                vec![0i32.into(), 5.into()],
915                Nullability::Nullable
916            )
917        );
918
919        assert!(
920            result
921                .is_invalid(1, &mut array_session().create_execution_ctx())
922                .unwrap()
923        );
924
925        assert!(
926            result
927                .is_valid(2, &mut array_session().create_execution_ctx())
928                .unwrap()
929        );
930        assert_eq!(
931            result
932                .execute_scalar(2, &mut array_session().create_execution_ctx())
933                .unwrap(),
934            Scalar::list(
935                Arc::clone(&element_dtype),
936                vec![3i32.into()],
937                Nullability::Nullable
938            )
939        );
940
941        assert!(
942            result
943                .is_valid(3, &mut array_session().create_execution_ctx())
944                .unwrap()
945        );
946        assert_eq!(
947            result
948                .execute_scalar(3, &mut array_session().create_execution_ctx())
949                .unwrap(),
950            Scalar::list(element_dtype, vec![], Nullability::Nullable)
951        );
952    }
953
954    #[test]
955    fn null_index_ignores_out_of_bounds_payload() {
956        let mut ctx = array_session().create_execution_ctx();
957        let list = ListArray::try_new(
958            buffer![1i32, 2, 3, 4].into_array(),
959            buffer![0u32, 2, 4].into_array(),
960            Validity::NonNullable,
961        )
962        .unwrap()
963        .into_array();
964
965        let idx = PrimitiveArray::new(
966            buffer![1u32, 99, 0],
967            Validity::from_iter([true, false, true]),
968        )
969        .into_array();
970        let result = list.take(idx).unwrap();
971
972        let expected = ListArray::new(
973            buffer![3i32, 4, 1, 2].into_array(),
974            buffer![0u32, 2, 2, 4].into_array(),
975            Validity::from_iter([true, false, true]),
976        );
977        assert_arrays_eq!(expected, result, &mut ctx);
978    }
979
980    #[test]
981    fn null_source_row_uses_valid_empty_output_range() {
982        let mut ctx = array_session().create_execution_ctx();
983        let list = ListArray::new(
984            buffer![1i32, 2, 7, 8].into_array(),
985            buffer![0u32, 2, 4].into_array(),
986            Validity::from_iter([true, false]),
987        )
988        .into_array();
989
990        let idx = buffer![0u32, 1].into_array();
991        let result = list.take(idx).unwrap();
992
993        let expected = ListArray::new(
994            buffer![1i32, 2].into_array(),
995            buffer![0u32, 2, 2].into_array(),
996            Validity::from_iter([true, false]),
997        );
998        assert_arrays_eq!(expected, result, &mut ctx);
999    }
1000
1001    #[test]
1002    fn change_validity() {
1003        let list = ListArray::try_new(
1004            buffer![0i32, 5, 3, 4].into_array(),
1005            buffer![0, 2, 3].into_array(),
1006            Validity::NonNullable,
1007        )
1008        .unwrap()
1009        .into_array();
1010
1011        let idx = PrimitiveArray::from_option_iter(vec![Some(0), Some(1), None]).into_array();
1012        // since idx is nullable, the final list will also be nullable
1013
1014        let result = list.take(idx).unwrap();
1015        assert_eq!(
1016            result.dtype(),
1017            &DType::List(
1018                Arc::new(DType::Primitive(I32, Nullability::NonNullable)),
1019                Nullability::Nullable
1020            )
1021        );
1022    }
1023
1024    #[test]
1025    fn non_nullable_take() {
1026        let mut ctx = array_session().create_execution_ctx();
1027        let list = ListArray::try_new(
1028            buffer![0i32, 5, 3, 4].into_array(),
1029            buffer![0, 2, 3, 3, 4].into_array(),
1030            Validity::NonNullable,
1031        )
1032        .unwrap()
1033        .into_array();
1034
1035        let idx = buffer![1, 0, 2].into_array();
1036
1037        let result = list.take(idx).unwrap();
1038
1039        assert_eq!(
1040            result.dtype(),
1041            &DType::List(
1042                Arc::new(DType::Primitive(I32, Nullability::NonNullable)),
1043                Nullability::NonNullable
1044            )
1045        );
1046
1047        let result = result.execute::<ListViewArray>(&mut ctx).unwrap();
1048
1049        assert_eq!(result.len(), 3);
1050
1051        let element_dtype: Arc<DType> = Arc::new(I32.into());
1052
1053        assert!(
1054            result
1055                .is_valid(0, &mut array_session().create_execution_ctx())
1056                .unwrap()
1057        );
1058        assert_eq!(
1059            result
1060                .execute_scalar(0, &mut array_session().create_execution_ctx())
1061                .unwrap(),
1062            Scalar::list(
1063                Arc::clone(&element_dtype),
1064                vec![3i32.into()],
1065                Nullability::NonNullable
1066            )
1067        );
1068
1069        assert!(
1070            result
1071                .is_valid(1, &mut array_session().create_execution_ctx())
1072                .unwrap()
1073        );
1074        assert_eq!(
1075            result
1076                .execute_scalar(1, &mut array_session().create_execution_ctx())
1077                .unwrap(),
1078            Scalar::list(
1079                Arc::clone(&element_dtype),
1080                vec![0i32.into(), 5.into()],
1081                Nullability::NonNullable
1082            )
1083        );
1084
1085        assert!(
1086            result
1087                .is_valid(2, &mut array_session().create_execution_ctx())
1088                .unwrap()
1089        );
1090        assert_eq!(
1091            result
1092                .execute_scalar(2, &mut array_session().create_execution_ctx())
1093                .unwrap(),
1094            Scalar::list(element_dtype, vec![], Nullability::NonNullable)
1095        );
1096    }
1097
1098    #[test]
1099    fn piecewise_sequence_take() {
1100        let mut ctx = array_session().create_execution_ctx();
1101        let list = ListArray::try_new(
1102            buffer![0i32, 1, 2, 3, 4, 5, 6].into_array(),
1103            buffer![0u32, 2, 5, 5, 7].into_array(),
1104            Validity::NonNullable,
1105        )
1106        .unwrap()
1107        .into_array();
1108        let idx = PiecewiseSequenceArray::try_new(
1109            buffer![1u64, 0].into_array(),
1110            buffer![2u64, 1].into_array(),
1111            ConstantArray::new(1u64, 2).into_array(),
1112            3,
1113        )
1114        .unwrap()
1115        .into_array();
1116
1117        let result = list
1118            .take(idx)
1119            .unwrap()
1120            .execute::<ListViewArray>(&mut ctx)
1121            .unwrap();
1122
1123        let element_dtype: Arc<DType> = Arc::new(I32.into());
1124        assert_eq!(
1125            result.execute_scalar(0, &mut ctx).unwrap(),
1126            Scalar::list(
1127                Arc::clone(&element_dtype),
1128                vec![2i32.into(), 3.into(), 4.into()],
1129                Nullability::NonNullable
1130            )
1131        );
1132        assert_eq!(
1133            result.execute_scalar(1, &mut ctx).unwrap(),
1134            Scalar::list(Arc::clone(&element_dtype), vec![], Nullability::NonNullable)
1135        );
1136        assert_eq!(
1137            result.execute_scalar(2, &mut ctx).unwrap(),
1138            Scalar::list(
1139                element_dtype,
1140                vec![0i32.into(), 1.into()],
1141                Nullability::NonNullable
1142            )
1143        );
1144    }
1145
1146    #[test]
1147    fn piecewise_sequence_take_nullable_list_constant_lengths() -> VortexResult<()> {
1148        let mut ctx = array_session().create_execution_ctx();
1149        let list = ListArray::try_new(
1150            buffer![0i32, 1, 99, 100, 2, 3, 4, 5].into_array(),
1151            buffer![0u32, 2, 4, 7, 8].into_array(),
1152            Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array()),
1153        )?
1154        .into_array();
1155        let idx = PiecewiseSequenceArray::try_new(
1156            buffer![0u64].into_array(),
1157            ConstantArray::new(4u64, 1).into_array(),
1158            ConstantArray::new(1u64, 1).into_array(),
1159            4,
1160        )?
1161        .into_array();
1162
1163        let result = list.take(idx)?.execute::<ListViewArray>(&mut ctx)?;
1164        assert_eq!(result.offset_at(0), 0);
1165        assert_eq!(result.size_at(0), 2);
1166        assert_eq!(result.offset_at(1), 2);
1167        assert_eq!(result.size_at(1), 0);
1168        assert_eq!(result.offset_at(2), 2);
1169        assert_eq!(result.size_at(2), 3);
1170        assert_eq!(result.offset_at(3), 5);
1171        assert_eq!(result.size_at(3), 1);
1172
1173        let element_dtype: Arc<DType> = Arc::new(I32.into());
1174        assert_eq!(
1175            result.execute_scalar(0, &mut ctx)?,
1176            Scalar::list(
1177                Arc::clone(&element_dtype),
1178                vec![0i32.into(), 1.into()],
1179                Nullability::Nullable
1180            )
1181        );
1182        assert!(result.is_invalid(1, &mut ctx)?);
1183        assert_eq!(
1184            result.execute_scalar(2, &mut ctx)?,
1185            Scalar::list(
1186                Arc::clone(&element_dtype),
1187                vec![2i32.into(), 3.into(), 4.into()],
1188                Nullability::Nullable
1189            )
1190        );
1191        assert_eq!(
1192            result.execute_scalar(3, &mut ctx)?,
1193            Scalar::list(element_dtype, vec![5i32.into()], Nullability::Nullable)
1194        );
1195        Ok(())
1196    }
1197
1198    #[test]
1199    fn piecewise_sequence_take_nullable_list_array_lengths() -> VortexResult<()> {
1200        let mut ctx = array_session().create_execution_ctx();
1201        let list = ListArray::try_new(
1202            buffer![0i32, 1, 99, 100, 2, 3, 4, 5].into_array(),
1203            buffer![0u32, 2, 4, 7, 8].into_array(),
1204            Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array()),
1205        )?
1206        .into_array();
1207        let idx = PiecewiseSequenceArray::try_new(
1208            buffer![1u64, 0].into_array(),
1209            buffer![2u64, 1].into_array(),
1210            ConstantArray::new(1u64, 2).into_array(),
1211            3,
1212        )?
1213        .into_array();
1214
1215        let result = list.take(idx)?.execute::<ListViewArray>(&mut ctx)?;
1216        assert_eq!(result.offset_at(0), 0);
1217        assert_eq!(result.size_at(0), 0);
1218        assert_eq!(result.offset_at(1), 0);
1219        assert_eq!(result.size_at(1), 3);
1220        assert_eq!(result.offset_at(2), 3);
1221        assert_eq!(result.size_at(2), 2);
1222
1223        let element_dtype: Arc<DType> = Arc::new(I32.into());
1224        assert!(result.is_invalid(0, &mut ctx)?);
1225        assert_eq!(
1226            result.execute_scalar(1, &mut ctx)?,
1227            Scalar::list(
1228                Arc::clone(&element_dtype),
1229                vec![2i32.into(), 3.into(), 4.into()],
1230                Nullability::Nullable
1231            )
1232        );
1233        assert_eq!(
1234            result.execute_scalar(2, &mut ctx)?,
1235            Scalar::list(
1236                element_dtype,
1237                vec![0i32.into(), 1.into()],
1238                Nullability::Nullable
1239            )
1240        );
1241        Ok(())
1242    }
1243
1244    #[test]
1245    fn test_take_empty_array() {
1246        let list = ListArray::try_new(
1247            buffer![0i32, 5, 3, 4].into_array(),
1248            buffer![0].into_array(),
1249            Validity::NonNullable,
1250        )
1251        .unwrap()
1252        .into_array();
1253
1254        let idx = PrimitiveArray::empty::<i32>(Nullability::Nullable).into_array();
1255
1256        let result = list.take(idx).unwrap();
1257        assert_eq!(
1258            result.dtype(),
1259            &DType::List(
1260                Arc::new(DType::Primitive(I32, Nullability::NonNullable)),
1261                Nullability::Nullable
1262            )
1263        );
1264        assert_eq!(result.len(), 0,);
1265    }
1266
1267    #[rstest]
1268    #[case(ListArray::try_new(
1269        buffer![0i32, 1, 2, 3, 4, 5].into_array(),
1270        buffer![0, 2, 3, 5, 5, 6].into_array(),
1271        Validity::NonNullable,
1272    ).unwrap())]
1273    #[case(ListArray::try_new(
1274        buffer![10i32, 20, 30, 40, 50].into_array(),
1275        buffer![0, 2, 3, 4, 5].into_array(),
1276        Validity::Array(BoolArray::from_iter(vec![true, false, true, true]).into_array()),
1277    ).unwrap())]
1278    #[case(ListArray::try_new(
1279        buffer![1i32, 2, 3].into_array(),
1280        buffer![0, 0, 2, 2, 3].into_array(), // First and third are empty
1281        Validity::NonNullable,
1282    ).unwrap())]
1283    #[case(ListArray::try_new(
1284        buffer![42i32, 43].into_array(),
1285        buffer![0, 2].into_array(),
1286        Validity::NonNullable,
1287    ).unwrap())]
1288    #[case({
1289        let elements = buffer![0i32..200].into_array();
1290        let mut offsets = vec![0u64];
1291        for i in 1..=50 {
1292            offsets.push(offsets[i - 1] + (i as u64 % 5)); // Variable length lists
1293        }
1294        ListArray::try_new(
1295            elements,
1296            PrimitiveArray::from_iter(offsets).into_array(),
1297            Validity::NonNullable,
1298        ).unwrap()
1299    })]
1300    #[case(ListArray::try_new(
1301        PrimitiveArray::from_option_iter([Some(1i32), None, Some(3), Some(4), None]).into_array(),
1302        buffer![0, 2, 3, 5].into_array(),
1303        Validity::NonNullable,
1304    ).unwrap())]
1305    fn test_take_list_conformance(#[case] list: ListArray) {
1306        test_take_conformance(
1307            &list.into_array(),
1308            &mut array_session().create_execution_ctx(),
1309        );
1310    }
1311
1312    #[test]
1313    fn test_u64_offset_accumulation_non_nullable() {
1314        let mut ctx = array_session().create_execution_ctx();
1315        let elements = buffer![0i32; 200].into_array();
1316        let offsets = buffer![0u8, 200].into_array();
1317        let list = ListArray::try_new(elements, offsets, Validity::NonNullable)
1318            .unwrap()
1319            .into_array();
1320
1321        // Take the same large list twice - would overflow u8 but works with u64.
1322        let idx = buffer![0u8, 0].into_array();
1323        let result = list.take(idx).unwrap();
1324
1325        assert_eq!(result.len(), 2);
1326
1327        let result_view = result.execute::<ListViewArray>(&mut ctx).unwrap();
1328        assert_eq!(result_view.len(), 2);
1329        assert!(
1330            result_view
1331                .is_valid(0, &mut array_session().create_execution_ctx())
1332                .unwrap()
1333        );
1334        assert!(
1335            result_view
1336                .is_valid(1, &mut array_session().create_execution_ctx())
1337                .unwrap()
1338        );
1339    }
1340
1341    #[test]
1342    fn test_u64_offset_accumulation_nullable() {
1343        let mut ctx = array_session().create_execution_ctx();
1344        let elements = buffer![0i32; 150].into_array();
1345        let offsets = buffer![0u8, 150, 150].into_array();
1346        let validity = BoolArray::from_iter(vec![true, false]).into_array();
1347        let list = ListArray::try_new(elements, offsets, Validity::Array(validity))
1348            .unwrap()
1349            .into_array();
1350
1351        // Take the same large list twice - would overflow u8 but works with u64.
1352        let idx = PrimitiveArray::from_option_iter(vec![Some(0u8), None, Some(0u8)]).into_array();
1353        let result = list.take(idx).unwrap();
1354
1355        assert_eq!(result.len(), 3);
1356
1357        let result_view = result.execute::<ListViewArray>(&mut ctx).unwrap();
1358        assert_eq!(result_view.len(), 3);
1359        assert!(
1360            result_view
1361                .is_valid(0, &mut array_session().create_execution_ctx())
1362                .unwrap()
1363        );
1364        assert!(
1365            result_view
1366                .is_invalid(1, &mut array_session().create_execution_ctx())
1367                .unwrap()
1368        );
1369        assert!(
1370            result_view
1371                .is_valid(2, &mut array_session().create_execution_ctx())
1372                .unwrap()
1373        );
1374    }
1375
1376    /// Regression test for validity length mismatch bug.
1377    ///
1378    /// When source array has `Validity::Array(...)` and indices are non-nullable,
1379    /// the result validity must have length equal to indices.len(), not source.len().
1380    #[test]
1381    fn test_take_validity_length_mismatch_regression() {
1382        // Source array with explicit validity array (length 2).
1383        let list = ListArray::try_new(
1384            buffer![1i32, 2, 3, 4].into_array(),
1385            buffer![0, 2, 4].into_array(),
1386            Validity::Array(BoolArray::from_iter(vec![true, true]).into_array()),
1387        )
1388        .unwrap()
1389        .into_array();
1390
1391        // Take more indices than source length (4 vs 2) with non-nullable indices.
1392        let idx = buffer![0u32, 1, 0, 1].into_array();
1393
1394        // This should not panic - result should have length 4.
1395        let result = list.take(idx).unwrap();
1396        assert_eq!(result.len(), 4);
1397    }
1398}