Skip to main content

vortex_array/arrays/varbin/compute/
take.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::iter;
5use std::sync::Arc;
6
7use itertools::Itertools as _;
8use num_traits::AsPrimitive;
9use vortex_buffer::BitBufferMut;
10use vortex_buffer::Buffer;
11use vortex_buffer::BufferMut;
12use vortex_buffer::ByteBufferMut;
13use vortex_error::VortexExpect;
14use vortex_error::VortexResult;
15use vortex_error::vortex_ensure;
16use vortex_error::vortex_ensure_eq;
17use vortex_error::vortex_err;
18use vortex_error::vortex_panic;
19use vortex_mask::AllOr;
20use vortex_mask::Mask;
21
22use crate::ArrayRef;
23use crate::Columnar;
24use crate::IntoArray;
25use crate::array::ArrayView;
26use crate::arrays::PiecewiseSequence;
27use crate::arrays::PrimitiveArray;
28use crate::arrays::VarBin;
29use crate::arrays::VarBinArray;
30use crate::arrays::VarBinViewArray;
31use crate::arrays::dict::TakeExecute;
32use crate::arrays::piecewise_sequence::constant_unsigned_usize;
33use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
34use crate::arrays::primitive::PrimitiveArrayExt;
35use crate::arrays::varbin::VarBinArrayExt;
36use crate::arrays::varbin::VarBinArraySlotsExt;
37use crate::arrays::varbinview::BinaryView;
38use crate::arrays::varbinview::build_views::MAX_BUFFER_LEN;
39use crate::dtype::DType;
40use crate::dtype::IntegerPType;
41use crate::dtype::PType;
42use crate::dtype::UnsignedPType;
43use crate::executor::ExecutionCtx;
44use crate::match_each_integer_ptype;
45use crate::match_each_unsigned_integer_ptype;
46use crate::validity::Validity;
47
48/// The widened offset type used for a taken `VarBinArray`: offsets are widened to at least 32 bits
49/// (to avoid overflow) while preserving signedness, so a signed result stays Arrow-compatible.
50fn taken_offset_ptype(offsets_ptype: PType) -> PType {
51    match offsets_ptype {
52        PType::U8 | PType::U16 | PType::U32 => PType::U32,
53        PType::U64 => PType::U64,
54        PType::I8 | PType::I16 | PType::I32 => PType::I32,
55        PType::I64 => PType::I64,
56        _ => unreachable!("invalid PType for offsets"),
57    }
58}
59
60// "take" can have offsets which don't fit in 32 bits. "signed" is needed for
61// Arrow interoperability.
62enum Offsets {
63    U32 {
64        offsets: BufferMut<u32>,
65        signed: bool,
66    },
67    U64 {
68        offsets: BufferMut<u64>,
69        signed: bool,
70    },
71}
72
73impl Offsets {
74    fn with_capacity(offset_ptype: PType, capacity: usize) -> Self {
75        let signed = offset_ptype.is_signed_int();
76        match offset_ptype {
77            PType::U64 | PType::I64 => Self::U64 {
78                offsets: BufferMut::with_capacity(capacity),
79                signed,
80            },
81            _ => Self::U32 {
82                offsets: BufferMut::with_capacity(capacity),
83                signed,
84            },
85        }
86    }
87
88    fn len(&self) -> usize {
89        match self {
90            Self::U32 { offsets, .. } => offsets.len(),
91            Self::U64 { offsets, .. } => offsets.len(),
92        }
93    }
94
95    fn push(&mut self, offset: usize) {
96        let (offsets, signed) = match self {
97            Self::U64 { offsets, .. } => {
98                offsets.push(offset as u64);
99                return;
100            }
101            Self::U32 { offsets, signed } => (offsets, *signed),
102        };
103        let max_narrow = if signed {
104            i32::MAX as usize
105        } else {
106            u32::MAX as usize
107        };
108        if offset <= max_narrow {
109            offsets.push(u32::try_from(offset).vortex_expect("offset fits u32"));
110            return;
111        }
112
113        // Copying the buffer may look scary but in fact it isn't.
114        // If we don't copy-and-widen, we need to know target width
115        // beforehand. This is approach taken in ListArray, where first pass
116        // iterates over offsets and determines target width, and second offset
117        // copies the offset to the buffer. Both have similar performance
118        // Note this happens only if 32-bit offset overflows which means array
119        // must hold >4GB of data which is quite rare.
120        let mut wide = BufferMut::<u64>::with_capacity(offsets.capacity() + 1);
121        wide.extend(offsets.iter().map(|&o| u64::from(o)));
122        wide.push(offset as u64);
123        *self = Self::U64 {
124            offsets: wide,
125            signed,
126        };
127    }
128
129    fn into_array(self) -> ArrayRef {
130        let (offsets, out_offset_ptype) = match self {
131            Self::U32 { offsets, signed } => (
132                PrimitiveArray::new(offsets.freeze(), Validity::NonNullable),
133                if signed { PType::I32 } else { PType::U32 },
134            ),
135            Self::U64 { offsets, signed } => (
136                PrimitiveArray::new(offsets.freeze(), Validity::NonNullable),
137                if signed { PType::I64 } else { PType::U64 },
138            ),
139        };
140        offsets.reinterpret_cast(out_offset_ptype).into_array()
141    }
142}
143
144impl TakeExecute for VarBin {
145    fn take(
146        array: ArrayView<'_, VarBin>,
147        indices: &ArrayRef,
148        ctx: &mut ExecutionCtx,
149    ) -> VortexResult<Option<ArrayRef>> {
150        let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
151        let offsets = offsets.reinterpret_cast(offsets.ptype().to_unsigned());
152        let last_offset = match_each_unsigned_integer_ptype!(offsets.ptype(), |O| {
153            offsets.as_slice::<O>().last().map_or(0usize, |&o| o.as_())
154        });
155
156        // VarBinView can't hold this buffer, so we can't canonicalize and
157        // take() (take panics). Convert to VarBin
158        if last_offset > MAX_BUFFER_LEN {
159            return Ok(Some(take_varbin(array, indices, ctx)?.into_array()));
160        }
161
162        let data = array.bytes().clone();
163        let dtype = array
164            .dtype()
165            .clone()
166            .union_nullability(indices.dtype().nullability());
167        let validity = array.validity()?.take(indices)?;
168
169        let indices = indices.clone().execute::<PrimitiveArray>(ctx)?;
170        let indices_mask = indices
171            .as_ref()
172            .validity()?
173            .execute_mask(indices.as_ref().len(), ctx)?;
174
175        let views = match_each_unsigned_integer_ptype!(offsets.ptype(), |O| {
176            match_each_integer_ptype!(indices.ptype(), |I| {
177                take_views(
178                    offsets.as_slice::<O>(),
179                    data.as_slice(),
180                    indices.as_slice::<I>(),
181                    &indices_mask,
182                )
183            })
184        });
185
186        // SAFETY: every view references buffer 0 which is inside shared data buffer
187        unsafe {
188            Ok(Some(
189                VarBinViewArray::new_unchecked(views, Arc::from([data]), dtype, validity)
190                    .into_array(),
191            ))
192        }
193    }
194}
195
196fn take_views<O: UnsignedPType, I: IntegerPType + AsPrimitive<usize>>(
197    offsets: &[O],
198    data: &[u8],
199    indices: &[I],
200    mask: &Mask,
201) -> Buffer<BinaryView> {
202    let build = |idx: usize| -> BinaryView {
203        let start: usize = offsets[idx].as_();
204        let stop: usize = offsets[idx + 1].as_();
205        let value = &data[start..stop];
206        let len = stop - start;
207
208        // Caller guarantees every offset is <= MAX_BUFFER_LEN
209        let start: u32 = start.as_();
210        if len > BinaryView::MAX_INLINED_SIZE {
211            let mut prefix = [0u8; 4];
212            prefix.copy_from_slice(&value[..4]);
213            let len: u32 = len.as_();
214            BinaryView::new_ref(len, prefix, 0, start)
215        } else {
216            BinaryView::make_view(value, 0, start)
217        }
218    };
219
220    match mask.bit_buffer() {
221        AllOr::All => Buffer::from_trusted_len_iter(indices.iter().map(|i| build(i.as_()))),
222        AllOr::None => {
223            Buffer::from_trusted_len_iter(iter::repeat_n(BinaryView::default(), indices.len()))
224        }
225        AllOr::Some(buffer) => {
226            Buffer::from_trusted_len_iter(buffer.iter().zip(indices.iter()).map(|(valid, i)| {
227                if valid {
228                    build(i.as_())
229                } else {
230                    BinaryView::default()
231                }
232            }))
233        }
234    }
235}
236
237/// Take from a VarBin. Referenced bytes are copied
238pub fn take_varbin(
239    array: ArrayView<'_, VarBin>,
240    indices: &ArrayRef,
241    ctx: &mut ExecutionCtx,
242) -> VortexResult<VarBinArray> {
243    if let Some(piecewise_indices) = indices.as_opt::<PiecewiseSequence>()
244        && let Some(taken) = take_contiguous_ranges(array, piecewise_indices, indices, ctx)?
245    {
246        return Ok(taken);
247    }
248
249    let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
250    let data = array.bytes();
251    let indices = indices.clone().execute::<PrimitiveArray>(ctx)?;
252    let dtype = array
253        .dtype()
254        .clone()
255        .union_nullability(indices.dtype().nullability());
256    let array_validity = array
257        .varbin_validity()
258        .execute_mask(array.as_ref().len(), ctx)?;
259    let indices_validity = indices
260        .as_ref()
261        .validity()?
262        .execute_mask(indices.as_ref().len(), ctx)?;
263
264    // Offsets and indices are non-negative; read them through their unsigned reinterpretations
265    // so we only monomorphize over the 4 unsigned widths each (4x4 instead of 8x8). On take,
266    // offsets get widened to either 32- or 64-bit (to avoid overflow); the built output offsets
267    // are reinterpreted back to `out_offset_ptype` to preserve the result's offset signedness.
268    let out_offset_ptype = taken_offset_ptype(offsets.ptype());
269    let offsets = offsets.reinterpret_cast(offsets.ptype().to_unsigned());
270    let indices = indices.reinterpret_cast(indices.ptype().to_unsigned());
271
272    match_each_unsigned_integer_ptype!(indices.ptype(), |I| {
273        match_each_unsigned_integer_ptype!(offsets.ptype(), |O| {
274            take::<I, O>(
275                dtype,
276                offsets.as_slice::<O>(),
277                data.as_slice(),
278                indices.as_slice::<I>(),
279                array_validity,
280                indices_validity,
281                out_offset_ptype,
282            )
283        })
284    })
285}
286
287fn take_contiguous_ranges(
288    array: ArrayView<'_, VarBin>,
289    indices: ArrayView<'_, PiecewiseSequence>,
290    indices_ref: &ArrayRef,
291    ctx: &mut ExecutionCtx,
292) -> VortexResult<Option<VarBinArray>> {
293    let Some((starts, lengths)) = maybe_contiguous_slices(indices, ctx)? else {
294        return Ok(None);
295    };
296    let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
297    let out_offset_ptype = taken_offset_ptype(offsets.ptype());
298    let offsets = offsets.reinterpret_cast(offsets.ptype().to_unsigned());
299    let bytes = array.bytes();
300    let data = bytes.as_slice();
301    let dtype = array.dtype().clone();
302    let output_len = indices_ref.len();
303
304    let result = match lengths {
305        Columnar::Constant(lengths) => {
306            let length = constant_unsigned_usize(&lengths);
307            gather_slices_constant_dispatch(
308                &starts,
309                length,
310                &offsets,
311                data,
312                output_len,
313                out_offset_ptype,
314            )?
315        }
316        Columnar::Canonical(lengths) => {
317            let lengths = lengths.into_primitive();
318            gather_slices_dispatch(
319                &starts,
320                &lengths,
321                &offsets,
322                data,
323                output_len,
324                out_offset_ptype,
325            )?
326        }
327    };
328
329    let validity = array.validity()?.take(indices_ref)?;
330
331    // SAFETY: output offsets are built from valid input offsets, start at zero, are monotonically
332    // non-decreasing, and the copied data buffer has exactly the referenced byte length.
333    unsafe {
334        Ok(Some(VarBinArray::new_unchecked(
335            result.offsets,
336            result.data.freeze(),
337            dtype,
338            validity,
339        )))
340    }
341}
342
343fn take<Index: IntegerPType, Offset: IntegerPType>(
344    dtype: DType,
345    offsets: &[Offset],
346    data: &[u8],
347    indices: &[Index],
348    validity_mask: Mask,
349    indices_validity_mask: Mask,
350    out_offset_ptype: PType,
351) -> VortexResult<VarBinArray> {
352    if !validity_mask.all_true() || !indices_validity_mask.all_true() {
353        return Ok(take_nullable::<Index, Offset>(
354            dtype,
355            offsets,
356            data,
357            indices,
358            validity_mask,
359            indices_validity_mask,
360            out_offset_ptype,
361        ));
362    }
363
364    let mut new_offsets = Offsets::with_capacity(out_offset_ptype, indices.len() + 1);
365    new_offsets.push(0);
366    let mut current_offset = 0usize;
367
368    for &idx in indices {
369        let idx = idx
370            .to_usize()
371            .unwrap_or_else(|| vortex_panic!("Failed to convert index to usize: {}", idx));
372        let start = offsets[idx];
373        let stop = offsets[idx + 1];
374
375        current_offset += (stop - start)
376            .to_usize()
377            .vortex_expect("Failed to cast offset to usize");
378        new_offsets.push(current_offset);
379    }
380
381    let mut new_data = ByteBufferMut::with_capacity(current_offset);
382
383    for idx in indices {
384        let idx = idx
385            .to_usize()
386            .unwrap_or_else(|| vortex_panic!("Failed to convert index to usize: {}", idx));
387        let start = offsets[idx]
388            .to_usize()
389            .vortex_expect("Failed to cast max offset to usize");
390        let stop = offsets[idx + 1]
391            .to_usize()
392            .vortex_expect("Failed to cast max offset to usize");
393        new_data.extend_from_slice(&data[start..stop]);
394    }
395
396    let array_validity = Validity::from(dtype.nullability());
397    let new_offsets = new_offsets.into_array();
398
399    // Safety:
400    // All variants of VarBinArray are satisfied here.
401    unsafe {
402        Ok(VarBinArray::new_unchecked(
403            new_offsets,
404            new_data.freeze(),
405            dtype,
406            array_validity,
407        ))
408    }
409}
410
411struct GatheredPiecewiseVarBin {
412    offsets: ArrayRef,
413    data: ByteBufferMut,
414}
415
416fn gather_slices_constant_dispatch(
417    starts: &PrimitiveArray,
418    length: usize,
419    offsets: &PrimitiveArray,
420    data: &[u8],
421    output_len: usize,
422    out_offset_ptype: PType,
423) -> VortexResult<GatheredPiecewiseVarBin> {
424    match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
425        gather_slices_constant_start_dispatch::<S>(
426            starts,
427            length,
428            offsets,
429            data,
430            output_len,
431            out_offset_ptype,
432        )
433    })
434}
435
436fn gather_slices_constant_start_dispatch<S>(
437    starts: &PrimitiveArray,
438    length: usize,
439    offsets: &PrimitiveArray,
440    data: &[u8],
441    output_len: usize,
442    out_offset_ptype: PType,
443) -> VortexResult<GatheredPiecewiseVarBin>
444where
445    S: UnsignedPType,
446{
447    match offsets.ptype() {
448        PType::U8 => gather_slices_constant_length::<S, u8>(
449            offsets.as_slice::<u8>(),
450            data,
451            starts.as_slice::<S>(),
452            length,
453            output_len,
454            out_offset_ptype,
455        ),
456        PType::U16 => gather_slices_constant_length::<S, u16>(
457            offsets.as_slice::<u16>(),
458            data,
459            starts.as_slice::<S>(),
460            length,
461            output_len,
462            out_offset_ptype,
463        ),
464        PType::U32 => gather_slices_constant_length::<S, u32>(
465            offsets.as_slice::<u32>(),
466            data,
467            starts.as_slice::<S>(),
468            length,
469            output_len,
470            out_offset_ptype,
471        ),
472        PType::U64 => gather_slices_constant_length::<S, u64>(
473            offsets.as_slice::<u64>(),
474            data,
475            starts.as_slice::<S>(),
476            length,
477            output_len,
478            out_offset_ptype,
479        ),
480        _ => unreachable!("offsets were reinterpreted to an unsigned integer ptype"),
481    }
482}
483
484fn gather_slices_dispatch(
485    starts: &PrimitiveArray,
486    lengths: &PrimitiveArray,
487    offsets: &PrimitiveArray,
488    data: &[u8],
489    output_len: usize,
490    out_offset_ptype: PType,
491) -> VortexResult<GatheredPiecewiseVarBin> {
492    match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
493        gather_slices_start_dispatch::<S>(
494            starts,
495            lengths,
496            offsets,
497            data,
498            output_len,
499            out_offset_ptype,
500        )
501    })
502}
503
504fn gather_slices_start_dispatch<S>(
505    starts: &PrimitiveArray,
506    lengths: &PrimitiveArray,
507    offsets: &PrimitiveArray,
508    data: &[u8],
509    output_len: usize,
510    out_offset_ptype: PType,
511) -> VortexResult<GatheredPiecewiseVarBin>
512where
513    S: UnsignedPType,
514{
515    match_each_unsigned_integer_ptype!(lengths.ptype(), |L| {
516        gather_slices_start_length_dispatch::<S, L>(
517            starts,
518            lengths,
519            offsets,
520            data,
521            output_len,
522            out_offset_ptype,
523        )
524    })
525}
526
527fn gather_slices_start_length_dispatch<S, L>(
528    starts: &PrimitiveArray,
529    lengths: &PrimitiveArray,
530    offsets: &PrimitiveArray,
531    data: &[u8],
532    output_len: usize,
533    out_offset_ptype: PType,
534) -> VortexResult<GatheredPiecewiseVarBin>
535where
536    S: UnsignedPType,
537    L: UnsignedPType,
538{
539    match offsets.ptype() {
540        PType::U8 => gather_slices::<S, L, u8>(
541            offsets.as_slice::<u8>(),
542            data,
543            starts.as_slice::<S>(),
544            lengths.as_slice::<L>(),
545            output_len,
546            out_offset_ptype,
547        ),
548        PType::U16 => gather_slices::<S, L, u16>(
549            offsets.as_slice::<u16>(),
550            data,
551            starts.as_slice::<S>(),
552            lengths.as_slice::<L>(),
553            output_len,
554            out_offset_ptype,
555        ),
556        PType::U32 => gather_slices::<S, L, u32>(
557            offsets.as_slice::<u32>(),
558            data,
559            starts.as_slice::<S>(),
560            lengths.as_slice::<L>(),
561            output_len,
562            out_offset_ptype,
563        ),
564        PType::U64 => gather_slices::<S, L, u64>(
565            offsets.as_slice::<u64>(),
566            data,
567            starts.as_slice::<S>(),
568            lengths.as_slice::<L>(),
569            output_len,
570            out_offset_ptype,
571        ),
572        _ => unreachable!("offsets were reinterpreted to an unsigned integer ptype"),
573    }
574}
575
576fn gather_slices_constant_length<S, Offset>(
577    offsets: &[Offset],
578    data: &[u8],
579    starts: &[S],
580    length: usize,
581    output_len: usize,
582    out_offset_ptype: PType,
583) -> VortexResult<GatheredPiecewiseVarBin>
584where
585    S: UnsignedPType,
586    Offset: IntegerPType,
587{
588    let computed_len = starts
589        .len()
590        .checked_mul(length)
591        .ok_or_else(|| vortex_err!("PiecewiseSequenceArray output length overflows usize"))?;
592    vortex_ensure_eq!(
593        computed_len,
594        output_len,
595        "PiecewiseSequenceArray expanded length does not match declared length",
596    );
597
598    let mut new_offsets = Offsets::with_capacity(out_offset_ptype, output_len + 1);
599    new_offsets.push(0);
600    let mut output_bytes = 0usize;
601
602    for start in starts {
603        let start = start.as_();
604        if length == 0 {
605            continue;
606        }
607
608        let offset_range = &offsets[start..][..=length];
609        let byte_start = offset_range[0].as_();
610        let byte_end = offset_range[length].as_();
611        vortex_ensure!(
612            byte_start <= byte_end && byte_end <= data.len(),
613            "VarBin offsets range {byte_start}..{byte_end} exceeds data length {}",
614            data.len()
615        );
616
617        for &offset in &offset_range[1..] {
618            let offset = offset.as_();
619            let relative = offset.checked_sub(byte_start).ok_or_else(|| {
620                vortex_err!("VarBin offsets are not monotonic at offset {offset}")
621            })?;
622            let output_offset = output_bytes.checked_add(relative).ok_or_else(|| {
623                vortex_err!("PiecewiseSequence VarBin output byte length overflow")
624            })?;
625            new_offsets.push(output_offset);
626        }
627
628        output_bytes = output_bytes
629            .checked_add(byte_end - byte_start)
630            .ok_or_else(|| vortex_err!("PiecewiseSequence VarBin output byte length overflow"))?;
631    }
632
633    let mut new_data = ByteBufferMut::with_capacity(output_bytes);
634    let spare = &mut new_data.spare_capacity_mut()[..output_bytes];
635    let mut cursor = 0usize;
636    for start in starts {
637        let start = start.as_();
638        if length == 0 {
639            continue;
640        }
641
642        let offset_range = &offsets[start..][..=length];
643        let byte_start = offset_range[0].as_();
644        let byte_end = offset_range[length].as_();
645        let src = &data[byte_start..byte_end];
646        spare[cursor..][..src.len()].write_copy_of_slice(src);
647        cursor += src.len();
648    }
649    // SAFETY: the loop initialized the prefix `0..cursor` of the spare capacity.
650    unsafe { new_data.set_len(cursor) };
651    vortex_ensure_eq!(
652        new_data.len(),
653        output_bytes,
654        "PiecewiseSequenceArray gathered byte length does not match declared byte length",
655    );
656
657    let offsets = new_offsets.into_array();
658    Ok(GatheredPiecewiseVarBin {
659        offsets,
660        data: new_data,
661    })
662}
663
664fn gather_slices<S, L, Offset>(
665    offsets: &[Offset],
666    data: &[u8],
667    starts: &[S],
668    lengths: &[L],
669    output_len: usize,
670    out_offset_ptype: PType,
671) -> VortexResult<GatheredPiecewiseVarBin>
672where
673    S: UnsignedPType,
674    L: UnsignedPType,
675    Offset: IntegerPType,
676{
677    let mut new_offsets = Offsets::with_capacity(out_offset_ptype, output_len + 1);
678    new_offsets.push(0);
679    let mut output_bytes = 0usize;
680
681    for (&start, &length) in starts.iter().zip_eq(lengths) {
682        let start = start.as_();
683        let length = length.as_();
684        if length == 0 {
685            continue;
686        }
687
688        let offset_range = &offsets[start..][..=length];
689        let byte_start = offset_range[0].as_();
690        let byte_end = offset_range[length].as_();
691        vortex_ensure!(
692            byte_start <= byte_end && byte_end <= data.len(),
693            "VarBin offsets range {byte_start}..{byte_end} exceeds data length {}",
694            data.len()
695        );
696
697        for &offset in &offset_range[1..] {
698            let offset = offset.as_();
699            let relative = offset.checked_sub(byte_start).ok_or_else(|| {
700                vortex_err!("VarBin offsets are not monotonic at offset {offset}")
701            })?;
702            let output_offset = output_bytes.checked_add(relative).ok_or_else(|| {
703                vortex_err!("PiecewiseSequence VarBin output byte length overflow")
704            })?;
705            new_offsets.push(output_offset);
706        }
707
708        output_bytes = output_bytes
709            .checked_add(byte_end - byte_start)
710            .ok_or_else(|| vortex_err!("PiecewiseSequence VarBin output byte length overflow"))?;
711    }
712    vortex_ensure_eq!(
713        new_offsets.len(),
714        output_len + 1,
715        "PiecewiseSequenceArray expanded offsets length does not match declared length plus one",
716    );
717
718    let mut new_data = ByteBufferMut::with_capacity(output_bytes);
719    let spare = &mut new_data.spare_capacity_mut()[..output_bytes];
720    let mut cursor = 0usize;
721    for (&start, &length) in starts.iter().zip_eq(lengths) {
722        let start = start.as_();
723        let length = length.as_();
724        if length == 0 {
725            continue;
726        }
727
728        let offset_range = &offsets[start..][..=length];
729        let byte_start = offset_range[0].as_();
730        let byte_end = offset_range[length].as_();
731        let src = &data[byte_start..byte_end];
732        spare[cursor..][..src.len()].write_copy_of_slice(src);
733        cursor += src.len();
734    }
735    // SAFETY: the loop initialized the prefix `0..cursor` of the spare capacity.
736    unsafe { new_data.set_len(cursor) };
737    vortex_ensure_eq!(
738        new_data.len(),
739        output_bytes,
740        "PiecewiseSequenceArray gathered byte length does not match declared byte length",
741    );
742
743    let offsets = new_offsets.into_array();
744    Ok(GatheredPiecewiseVarBin {
745        offsets,
746        data: new_data,
747    })
748}
749
750fn take_nullable<Index: IntegerPType, Offset: IntegerPType>(
751    dtype: DType,
752    offsets: &[Offset],
753    data: &[u8],
754    indices: &[Index],
755    data_validity: Mask,
756    indices_validity: Mask,
757    out_offset_ptype: PType,
758) -> VarBinArray {
759    let mut new_offsets = Offsets::with_capacity(out_offset_ptype, indices.len() + 1);
760    new_offsets.push(0);
761    let mut current_offset = 0usize;
762
763    let mut validity_buffer = BitBufferMut::with_capacity(indices.len());
764
765    // Convert indices once and store valid ones with their positions
766    let mut valid_indices = Vec::with_capacity(indices.len());
767
768    // First pass: calculate offsets and validity
769    for (data_idx, index_valid) in indices.iter().zip(indices_validity.iter()) {
770        if !index_valid {
771            validity_buffer.append(false);
772            new_offsets.push(current_offset);
773            continue;
774        }
775        let data_idx_usize = data_idx
776            .to_usize()
777            .unwrap_or_else(|| vortex_panic!("Failed to convert index to usize: {}", data_idx));
778        if data_validity.value(data_idx_usize) {
779            validity_buffer.append(true);
780            let start = offsets[data_idx_usize];
781            let stop = offsets[data_idx_usize + 1];
782            current_offset += (stop - start)
783                .to_usize()
784                .vortex_expect("Failed to cast offset to usize");
785            new_offsets.push(current_offset);
786            valid_indices.push(data_idx_usize);
787        } else {
788            validity_buffer.append(false);
789            new_offsets.push(current_offset);
790        }
791    }
792
793    let mut new_data = ByteBufferMut::with_capacity(current_offset);
794
795    // Second pass: copy data for valid indices only
796    for data_idx in valid_indices {
797        let start = offsets[data_idx]
798            .to_usize()
799            .vortex_expect("Failed to cast max offset to usize");
800        let stop = offsets[data_idx + 1]
801            .to_usize()
802            .vortex_expect("Failed to cast max offset to usize");
803        new_data.extend_from_slice(&data[start..stop]);
804    }
805
806    let array_validity = Validity::from(validity_buffer.freeze());
807    let new_offsets = new_offsets.into_array();
808
809    // Safety:
810    // All variants of VarBinArray are satisfied here.
811    unsafe { VarBinArray::new_unchecked(new_offsets, new_data.freeze(), dtype, array_validity) }
812}
813
814#[cfg(test)]
815mod tests {
816    use std::iter;
817
818    use rstest::rstest;
819    use vortex_buffer::ByteBuffer;
820    use vortex_buffer::buffer;
821    use vortex_error::VortexResult;
822
823    use crate::IntoArray;
824    use crate::VortexSessionExecute;
825    use crate::array_session;
826    use crate::arrays::VarBinArray;
827    use crate::arrays::VarBinViewArray;
828    use crate::arrays::varbin::compute::take::PrimitiveArray;
829    use crate::assert_arrays_eq;
830    use crate::compute::conformance::take::test_take_conformance;
831    use crate::dtype::DType;
832    use crate::dtype::Nullability;
833    use crate::dtype::PType;
834    use crate::validity::Validity;
835
836    #[test]
837    fn test_null_take() {
838        let arr = VarBinArray::from_iter([Some("h")], DType::Utf8(Nullability::NonNullable));
839
840        let idx1: PrimitiveArray = (0..1).collect();
841
842        assert_eq!(
843            arr.take(idx1.into_array()).unwrap().dtype(),
844            &DType::Utf8(Nullability::NonNullable)
845        );
846
847        let idx2: PrimitiveArray = PrimitiveArray::from_option_iter(vec![Some(0)]);
848
849        assert_eq!(
850            arr.take(idx2.into_array()).unwrap().dtype(),
851            &DType::Utf8(Nullability::Nullable)
852        );
853    }
854
855    #[rstest]
856    #[case(VarBinArray::from_iter(
857        ["hello", "world", "test", "data", "array"].map(Some),
858        DType::Utf8(Nullability::NonNullable),
859    ))]
860    #[case(VarBinArray::from_iter(
861        [Some("hello"), None, Some("test"), Some("data"), None],
862        DType::Utf8(Nullability::Nullable),
863    ))]
864    #[case(VarBinArray::from_iter(
865        [b"hello".as_slice(), b"world", b"test", b"data", b"array"].map(Some),
866        DType::Binary(Nullability::NonNullable),
867    ))]
868    #[case(VarBinArray::from_iter(["single"].map(Some), DType::Utf8(Nullability::NonNullable)))]
869    fn test_take_varbin_conformance(#[case] array: VarBinArray) {
870        test_take_conformance(
871            &array.into_array(),
872            &mut array_session().create_execution_ctx(),
873        );
874    }
875
876    #[test]
877    fn test_take_overflow() {
878        let mut ctx = array_session().create_execution_ctx();
879        let scream = iter::once("a").cycle().take(128).collect::<String>();
880        let bytes = ByteBuffer::copy_from(scream.as_bytes());
881        let offsets = buffer![0u8, 128u8].into_array();
882
883        let array = VarBinArray::new(
884            offsets,
885            bytes,
886            DType::Utf8(Nullability::NonNullable),
887            Validity::NonNullable,
888        );
889
890        let indices = buffer![0u32; 3].into_array();
891        let taken = array.take(indices).unwrap();
892
893        let expected = VarBinViewArray::from_iter(
894            [Some(scream.clone()), Some(scream.clone()), Some(scream)],
895            DType::Utf8(Nullability::NonNullable),
896        );
897        assert_arrays_eq!(expected, taken, &mut ctx);
898    }
899
900    #[rstest]
901    #[case(PType::U32, u32::MAX as usize, PType::U32)]
902    #[case(PType::U32, u32::MAX as usize + 1, PType::U64)]
903    #[case(PType::I32, i32::MAX as usize, PType::I32)]
904    #[case(PType::I32, i32::MAX as usize + 1, PType::I64)]
905    #[case(PType::U64, 8, PType::U64)]
906    #[case(PType::I64, 8, PType::I64)]
907    fn test_offsets_widen(
908        #[case] out_offset_ptype: PType,
909        #[case] max_offset: usize,
910        #[case] expected: PType,
911    ) {
912        let mut sink = super::Offsets::with_capacity(out_offset_ptype, 2);
913        sink.push(0);
914        sink.push(max_offset);
915        assert_eq!(
916            PType::try_from(sink.into_array().dtype()).unwrap(),
917            expected
918        );
919    }
920
921    #[test]
922    fn test_offset_widen_values() -> VortexResult<()> {
923        let mut ctx = array_session().create_execution_ctx();
924        let big = u32::MAX as usize + 5;
925        let mut sink = super::Offsets::with_capacity(PType::U32, 3);
926        sink.push(0);
927        sink.push(7);
928        sink.push(big);
929
930        let array = sink.into_array().execute::<PrimitiveArray>(&mut ctx)?;
931        assert_eq!(array.as_slice::<u64>(), &[0, 7, big as u64]);
932        Ok(())
933    }
934
935    #[test]
936    fn test_take_signed() {
937        let mut ctx = array_session().create_execution_ctx();
938        let array = VarBinArray::new(
939            buffer![0i32, 5, 10].into_array(),
940            ByteBuffer::copy_from(b"helloworld"),
941            DType::Utf8(Nullability::NonNullable),
942            Validity::NonNullable,
943        );
944
945        let taken = array.take(buffer![1u32, 0].into_array()).unwrap();
946
947        let expected = VarBinViewArray::from_iter(
948            [Some("world"), Some("hello")],
949            DType::Utf8(Nullability::NonNullable),
950        );
951        assert_arrays_eq!(expected, taken, &mut ctx);
952    }
953}