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::ptr;
6use std::sync::Arc;
7
8use itertools::Itertools as _;
9use num_traits::AsPrimitive;
10use vortex_buffer::BitBufferMut;
11use vortex_buffer::Buffer;
12use vortex_buffer::BufferMut;
13use vortex_buffer::ByteBufferMut;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_ensure;
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!(
593        computed_len == output_len,
594        "PiecewiseSequenceArray expanded length {computed_len} does not match declared length {output_len}"
595    );
596
597    let mut new_offsets = Offsets::with_capacity(out_offset_ptype, output_len + 1);
598    new_offsets.push(0);
599    let mut output_bytes = 0usize;
600
601    for start in starts {
602        let start = start.as_();
603        if length == 0 {
604            continue;
605        }
606
607        let offset_range = &offsets[start..][..=length];
608        let byte_start = offset_range[0].as_();
609        let byte_end = offset_range[length].as_();
610        vortex_ensure!(
611            byte_start <= byte_end && byte_end <= data.len(),
612            "VarBin offsets range {byte_start}..{byte_end} exceeds data length {}",
613            data.len()
614        );
615
616        for &offset in &offset_range[1..] {
617            let offset = offset.as_();
618            let relative = offset.checked_sub(byte_start).ok_or_else(|| {
619                vortex_err!("VarBin offsets are not monotonic at offset {offset}")
620            })?;
621            let output_offset = output_bytes.checked_add(relative).ok_or_else(|| {
622                vortex_err!("PiecewiseSequence VarBin output byte length overflow")
623            })?;
624            new_offsets.push(output_offset);
625        }
626
627        output_bytes = output_bytes
628            .checked_add(byte_end - byte_start)
629            .ok_or_else(|| vortex_err!("PiecewiseSequence VarBin output byte length overflow"))?;
630    }
631
632    let mut new_data = ByteBufferMut::with_capacity(output_bytes);
633    let spare = &mut new_data.spare_capacity_mut()[..output_bytes];
634    let mut cursor = 0usize;
635    for start in starts {
636        let start = start.as_();
637        if length == 0 {
638            continue;
639        }
640
641        let offset_range = &offsets[start..][..=length];
642        let byte_start = offset_range[0].as_();
643        let byte_end = offset_range[length].as_();
644        let src = &data[byte_start..byte_end];
645        // SAFETY: `src` and the checked `spare` range have equal lengths and cannot overlap.
646        unsafe {
647            ptr::copy_nonoverlapping(
648                src.as_ptr(),
649                spare[cursor..][..src.len()].as_mut_ptr().cast::<u8>(),
650                src.len(),
651            );
652        }
653        cursor += src.len();
654    }
655    // SAFETY: the loop initialized the prefix `0..cursor` of the spare capacity.
656    unsafe { new_data.set_len(cursor) };
657    vortex_ensure!(
658        new_data.len() == output_bytes,
659        "PiecewiseSequenceArray gathered byte length {} does not match declared byte length {output_bytes}",
660        new_data.len()
661    );
662
663    let offsets = new_offsets.into_array();
664    Ok(GatheredPiecewiseVarBin {
665        offsets,
666        data: new_data,
667    })
668}
669
670fn gather_slices<S, L, Offset>(
671    offsets: &[Offset],
672    data: &[u8],
673    starts: &[S],
674    lengths: &[L],
675    output_len: usize,
676    out_offset_ptype: PType,
677) -> VortexResult<GatheredPiecewiseVarBin>
678where
679    S: UnsignedPType,
680    L: UnsignedPType,
681    Offset: IntegerPType,
682{
683    let mut new_offsets = Offsets::with_capacity(out_offset_ptype, output_len + 1);
684    new_offsets.push(0);
685    let mut output_bytes = 0usize;
686
687    for (&start, &length) in starts.iter().zip_eq(lengths) {
688        let start = start.as_();
689        let length = length.as_();
690        if length == 0 {
691            continue;
692        }
693
694        let offset_range = &offsets[start..][..=length];
695        let byte_start = offset_range[0].as_();
696        let byte_end = offset_range[length].as_();
697        vortex_ensure!(
698            byte_start <= byte_end && byte_end <= data.len(),
699            "VarBin offsets range {byte_start}..{byte_end} exceeds data length {}",
700            data.len()
701        );
702
703        for &offset in &offset_range[1..] {
704            let offset = offset.as_();
705            let relative = offset.checked_sub(byte_start).ok_or_else(|| {
706                vortex_err!("VarBin offsets are not monotonic at offset {offset}")
707            })?;
708            let output_offset = output_bytes.checked_add(relative).ok_or_else(|| {
709                vortex_err!("PiecewiseSequence VarBin output byte length overflow")
710            })?;
711            new_offsets.push(output_offset);
712        }
713
714        output_bytes = output_bytes
715            .checked_add(byte_end - byte_start)
716            .ok_or_else(|| vortex_err!("PiecewiseSequence VarBin output byte length overflow"))?;
717    }
718    vortex_ensure!(
719        new_offsets.len() == output_len + 1,
720        "PiecewiseSequenceArray expanded length {} does not match declared length {output_len}",
721        new_offsets.len() - 1
722    );
723
724    let mut new_data = ByteBufferMut::with_capacity(output_bytes);
725    let spare = &mut new_data.spare_capacity_mut()[..output_bytes];
726    let mut cursor = 0usize;
727    for (&start, &length) in starts.iter().zip_eq(lengths) {
728        let start = start.as_();
729        let length = length.as_();
730        if length == 0 {
731            continue;
732        }
733
734        let offset_range = &offsets[start..][..=length];
735        let byte_start = offset_range[0].as_();
736        let byte_end = offset_range[length].as_();
737        let src = &data[byte_start..byte_end];
738        // SAFETY: `src` and the checked `spare` range have equal lengths and cannot overlap.
739        unsafe {
740            ptr::copy_nonoverlapping(
741                src.as_ptr(),
742                spare[cursor..][..src.len()].as_mut_ptr().cast::<u8>(),
743                src.len(),
744            );
745        }
746        cursor += src.len();
747    }
748    // SAFETY: the loop initialized the prefix `0..cursor` of the spare capacity.
749    unsafe { new_data.set_len(cursor) };
750    vortex_ensure!(
751        new_data.len() == output_bytes,
752        "PiecewiseSequenceArray gathered byte length {} does not match declared byte length {output_bytes}",
753        new_data.len()
754    );
755
756    let offsets = new_offsets.into_array();
757    Ok(GatheredPiecewiseVarBin {
758        offsets,
759        data: new_data,
760    })
761}
762
763fn take_nullable<Index: IntegerPType, Offset: IntegerPType>(
764    dtype: DType,
765    offsets: &[Offset],
766    data: &[u8],
767    indices: &[Index],
768    data_validity: Mask,
769    indices_validity: Mask,
770    out_offset_ptype: PType,
771) -> VarBinArray {
772    let mut new_offsets = Offsets::with_capacity(out_offset_ptype, indices.len() + 1);
773    new_offsets.push(0);
774    let mut current_offset = 0usize;
775
776    let mut validity_buffer = BitBufferMut::with_capacity(indices.len());
777
778    // Convert indices once and store valid ones with their positions
779    let mut valid_indices = Vec::with_capacity(indices.len());
780
781    // First pass: calculate offsets and validity
782    for (data_idx, index_valid) in indices.iter().zip(indices_validity.iter()) {
783        if !index_valid {
784            validity_buffer.append(false);
785            new_offsets.push(current_offset);
786            continue;
787        }
788        let data_idx_usize = data_idx
789            .to_usize()
790            .unwrap_or_else(|| vortex_panic!("Failed to convert index to usize: {}", data_idx));
791        if data_validity.value(data_idx_usize) {
792            validity_buffer.append(true);
793            let start = offsets[data_idx_usize];
794            let stop = offsets[data_idx_usize + 1];
795            current_offset += (stop - start)
796                .to_usize()
797                .vortex_expect("Failed to cast offset to usize");
798            new_offsets.push(current_offset);
799            valid_indices.push(data_idx_usize);
800        } else {
801            validity_buffer.append(false);
802            new_offsets.push(current_offset);
803        }
804    }
805
806    let mut new_data = ByteBufferMut::with_capacity(current_offset);
807
808    // Second pass: copy data for valid indices only
809    for data_idx in valid_indices {
810        let start = offsets[data_idx]
811            .to_usize()
812            .vortex_expect("Failed to cast max offset to usize");
813        let stop = offsets[data_idx + 1]
814            .to_usize()
815            .vortex_expect("Failed to cast max offset to usize");
816        new_data.extend_from_slice(&data[start..stop]);
817    }
818
819    let array_validity = Validity::from(validity_buffer.freeze());
820    let new_offsets = new_offsets.into_array();
821
822    // Safety:
823    // All variants of VarBinArray are satisfied here.
824    unsafe { VarBinArray::new_unchecked(new_offsets, new_data.freeze(), dtype, array_validity) }
825}
826
827#[cfg(test)]
828mod tests {
829    use std::iter;
830
831    use rstest::rstest;
832    use vortex_buffer::ByteBuffer;
833    use vortex_buffer::buffer;
834    use vortex_error::VortexResult;
835
836    use crate::IntoArray;
837    use crate::VortexSessionExecute;
838    use crate::array_session;
839    use crate::arrays::VarBinArray;
840    use crate::arrays::VarBinViewArray;
841    use crate::arrays::varbin::compute::take::PrimitiveArray;
842    use crate::assert_arrays_eq;
843    use crate::compute::conformance::take::test_take_conformance;
844    use crate::dtype::DType;
845    use crate::dtype::Nullability;
846    use crate::dtype::PType;
847    use crate::validity::Validity;
848
849    #[test]
850    fn test_null_take() {
851        let arr = VarBinArray::from_iter([Some("h")], DType::Utf8(Nullability::NonNullable));
852
853        let idx1: PrimitiveArray = (0..1).collect();
854
855        assert_eq!(
856            arr.take(idx1.into_array()).unwrap().dtype(),
857            &DType::Utf8(Nullability::NonNullable)
858        );
859
860        let idx2: PrimitiveArray = PrimitiveArray::from_option_iter(vec![Some(0)]);
861
862        assert_eq!(
863            arr.take(idx2.into_array()).unwrap().dtype(),
864            &DType::Utf8(Nullability::Nullable)
865        );
866    }
867
868    #[rstest]
869    #[case(VarBinArray::from_iter(
870        ["hello", "world", "test", "data", "array"].map(Some),
871        DType::Utf8(Nullability::NonNullable),
872    ))]
873    #[case(VarBinArray::from_iter(
874        [Some("hello"), None, Some("test"), Some("data"), None],
875        DType::Utf8(Nullability::Nullable),
876    ))]
877    #[case(VarBinArray::from_iter(
878        [b"hello".as_slice(), b"world", b"test", b"data", b"array"].map(Some),
879        DType::Binary(Nullability::NonNullable),
880    ))]
881    #[case(VarBinArray::from_iter(["single"].map(Some), DType::Utf8(Nullability::NonNullable)))]
882    fn test_take_varbin_conformance(#[case] array: VarBinArray) {
883        test_take_conformance(
884            &array.into_array(),
885            &mut array_session().create_execution_ctx(),
886        );
887    }
888
889    #[test]
890    fn test_take_overflow() {
891        let mut ctx = array_session().create_execution_ctx();
892        let scream = iter::once("a").cycle().take(128).collect::<String>();
893        let bytes = ByteBuffer::copy_from(scream.as_bytes());
894        let offsets = buffer![0u8, 128u8].into_array();
895
896        let array = VarBinArray::new(
897            offsets,
898            bytes,
899            DType::Utf8(Nullability::NonNullable),
900            Validity::NonNullable,
901        );
902
903        let indices = buffer![0u32; 3].into_array();
904        let taken = array.take(indices).unwrap();
905
906        let expected = VarBinViewArray::from_iter(
907            [Some(scream.clone()), Some(scream.clone()), Some(scream)],
908            DType::Utf8(Nullability::NonNullable),
909        );
910        assert_arrays_eq!(expected, taken, &mut ctx);
911    }
912
913    #[rstest]
914    #[case(PType::U32, u32::MAX as usize, PType::U32)]
915    #[case(PType::U32, u32::MAX as usize + 1, PType::U64)]
916    #[case(PType::I32, i32::MAX as usize, PType::I32)]
917    #[case(PType::I32, i32::MAX as usize + 1, PType::I64)]
918    #[case(PType::U64, 8, PType::U64)]
919    #[case(PType::I64, 8, PType::I64)]
920    fn test_offsets_widen(
921        #[case] out_offset_ptype: PType,
922        #[case] max_offset: usize,
923        #[case] expected: PType,
924    ) {
925        let mut sink = super::Offsets::with_capacity(out_offset_ptype, 2);
926        sink.push(0);
927        sink.push(max_offset);
928        assert_eq!(
929            PType::try_from(sink.into_array().dtype()).unwrap(),
930            expected
931        );
932    }
933
934    #[test]
935    fn test_offset_widen_values() -> VortexResult<()> {
936        let mut ctx = array_session().create_execution_ctx();
937        let big = u32::MAX as usize + 5;
938        let mut sink = super::Offsets::with_capacity(PType::U32, 3);
939        sink.push(0);
940        sink.push(7);
941        sink.push(big);
942
943        let array = sink.into_array().execute::<PrimitiveArray>(&mut ctx)?;
944        assert_eq!(array.as_slice::<u64>(), &[0, 7, big as u64]);
945        Ok(())
946    }
947
948    #[test]
949    fn test_take_signed() {
950        let mut ctx = array_session().create_execution_ctx();
951        let array = VarBinArray::new(
952            buffer![0i32, 5, 10].into_array(),
953            ByteBuffer::copy_from(b"helloworld"),
954            DType::Utf8(Nullability::NonNullable),
955            Validity::NonNullable,
956        );
957
958        let taken = array.take(buffer![1u32, 0].into_array()).unwrap();
959
960        let expected = VarBinViewArray::from_iter(
961            [Some("world"), Some("hello")],
962            DType::Utf8(Nullability::NonNullable),
963        );
964        assert_arrays_eq!(expected, taken, &mut ctx);
965    }
966}