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