Skip to main content

vortex_array/arrays/varbin/
builder.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::mem::MaybeUninit;
6
7use num_traits::AsPrimitive;
8use vortex_buffer::Alignment;
9use vortex_buffer::BitBufferMut;
10use vortex_buffer::BufferAllocatorRef;
11use vortex_buffer::BufferMut;
12use vortex_buffer::ByteBuffer;
13use vortex_buffer::ByteBufferMut;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_bail;
17use vortex_error::vortex_ensure;
18use vortex_error::vortex_panic;
19use vortex_mask::AllOr;
20use vortex_mask::Mask;
21
22use crate::ArrayRef;
23use crate::ArrayView;
24use crate::Canonical;
25use crate::ExecutionCtx;
26use crate::IntoArray;
27#[cfg(debug_assertions)]
28use crate::VortexSessionExecute;
29use crate::arrays::PrimitiveArray;
30use crate::arrays::VarBin;
31use crate::arrays::VarBinArray;
32use crate::arrays::VarBinView;
33use crate::arrays::varbin::VarBinArrayExt;
34use crate::arrays::varbin::VarBinArraySlotsExt;
35use crate::arrays::varbinview::VarBinViewArrayExt;
36use crate::builders::ArrayBuilder;
37use crate::dtype::DType;
38use crate::dtype::OffsetBuilderPType;
39use crate::expr::stats::Precision;
40use crate::expr::stats::Stat;
41#[cfg(debug_assertions)]
42use crate::legacy_session;
43use crate::match_each_integer_ptype;
44use crate::scalar::Scalar;
45use crate::validity::Validity;
46
47/// Builder for [`VarBinArray`] values with `O`-typed offsets.
48///
49/// This is the offset-based counterpart to
50/// [`VarBinViewBuilder`](crate::builders::VarBinViewBuilder): values are laid out contiguously in
51/// a single byte buffer described by a monotonically increasing offsets buffer. An `i32` or `i64`
52/// builder can therefore be handed straight to Arrow as a `Utf8`/`LargeUtf8`/`Binary`/
53/// `LargeBinary` array without re-laying out the bytes.
54///
55/// Encodings that can decode into this layout should specialize
56/// [`append_to_builder`](crate::vtable::VTable::append_to_builder) with
57/// [`match_each_varbin_builder!`](crate::match_each_varbin_builder), which recovers the concrete
58/// offset type from a `&mut dyn ArrayBuilder` so the decode loop is monomorphized over it.
59pub struct VarBinBuilder<O: OffsetBuilderPType> {
60    dtype: DType,
61    offsets: BufferMut<O>,
62    data: ByteBufferMut,
63    validity: BitBufferMut,
64}
65
66impl<O: OffsetBuilderPType> VarBinBuilder<O> {
67    /// Creates an empty builder for `dtype`.
68    #[deprecated(note = "use `new_in` with an explicit allocator")]
69    pub fn new(dtype: DType) -> Self {
70        Self::new_in(dtype, BufferAllocatorRef::static_ref())
71    }
72
73    /// Creates an empty builder for `dtype` using `allocator`.
74    pub fn new_in(dtype: DType, allocator: &BufferAllocatorRef) -> Self {
75        Self::with_capacity_in(dtype, 0, allocator)
76    }
77
78    /// Creates a builder for `dtype` with room for `capacity` values.
79    #[deprecated(note = "use `with_capacity_in` with an explicit allocator")]
80    pub fn with_capacity(dtype: DType, capacity: usize) -> Self {
81        Self::with_capacity_in(dtype, capacity, BufferAllocatorRef::static_ref())
82    }
83
84    /// Creates a builder for `dtype` with room for `capacity` values using `allocator`.
85    pub fn with_capacity_in(dtype: DType, capacity: usize, allocator: &BufferAllocatorRef) -> Self {
86        assert!(
87            matches!(dtype, DType::Utf8(_) | DType::Binary(_)),
88            "VarBinBuilder dtype must be Utf8 or Binary, got {dtype}"
89        );
90        let mut offsets = BufferMut::with_capacity_in(capacity + 1, allocator.clone());
91        offsets.push(O::zero());
92        Self {
93            dtype,
94            offsets,
95            data: BufferMut::empty_aligned_in(Alignment::of::<u8>(), allocator.clone()),
96            validity: BitBufferMut::with_capacity_in(capacity, allocator.clone()),
97        }
98    }
99
100    /// Creates a builder for `dtype` with room for `capacity` values totalling `bytes` bytes.
101    #[deprecated(note = "use `with_capacity_bytes_in` with an explicit allocator")]
102    pub fn with_capacity_bytes(dtype: DType, capacity: usize, bytes: usize) -> Self {
103        Self::with_capacity_bytes_in(dtype, capacity, bytes, BufferAllocatorRef::static_ref())
104    }
105
106    /// Creates a builder using `allocator` with room for `capacity` values and `bytes` bytes.
107    pub fn with_capacity_bytes_in(
108        dtype: DType,
109        capacity: usize,
110        bytes: usize,
111        allocator: &BufferAllocatorRef,
112    ) -> Self {
113        let mut builder = Self::with_capacity_in(dtype, capacity, allocator);
114        builder.reserve_data(bytes);
115        builder
116    }
117
118    /// Reserves room for `additional` value bytes.
119    ///
120    /// [`reserve_exact`](ArrayBuilder::reserve_exact) takes a row count and so cannot size the
121    /// value bytes; callers that know the byte total should use this to size the buffer once
122    /// instead of letting it grow.
123    pub fn reserve_data(&mut self, additional: usize) {
124        self.data.reserve(additional);
125    }
126
127    /// Appends one value, or a null when it is `None`.
128    ///
129    /// # Panics
130    ///
131    /// Panics if the resulting end offset does not fit in `O`. See
132    /// [`append_value`](Self::append_value).
133    #[inline]
134    pub fn append(&mut self, value: Option<&[u8]>) {
135        match value {
136            Some(v) => self.append_value(v),
137            None => self.push_null(),
138        }
139    }
140
141    /// Appends one non-null value.
142    ///
143    /// # Panics
144    ///
145    /// Panics if the resulting end offset does not fit in `O`. The bulk appends report that as an
146    /// error instead; use [`append_n_values`](Self::append_n_values) for a fallible single append,
147    /// or an `i64` builder for byte totals past `i32::MAX`.
148    #[inline]
149    pub fn append_value(&mut self, value: impl AsRef<[u8]>) {
150        self.push_value(value.as_ref());
151        self.validity.append_true();
152    }
153
154    /// Appends the same non-null value `n` times.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error, leaving the builder unchanged, if the resulting end offsets do not fit
159    /// in `O`.
160    pub fn append_n_values(&mut self, value: impl AsRef<[u8]>, n: usize) -> VortexResult<()> {
161        let value = value.as_ref();
162        let Some(num_bytes) = value.len().checked_mul(n) else {
163            vortex_bail!("Byte count overflow: {} values of {} bytes", n, value.len());
164        };
165        // Checking the total up front is what keeps `push_value` below from panicking.
166        self.check_offset_limit(self.data.len(), num_bytes)?;
167        self.offsets.reserve(n);
168        self.data.reserve(num_bytes);
169        for _ in 0..n {
170            self.push_value(value);
171        }
172        self.validity.append_n(true, n);
173        Ok(())
174    }
175
176    /// Appends a null value.
177    ///
178    /// Unlike [`append_null`](ArrayBuilder::append_null) this does not check that the builder is
179    /// nullable; the offsets and validity stay consistent either way, and a non-nullable `dtype`
180    /// discards the validity bits on [`finish_into_varbin`](Self::finish_into_varbin).
181    #[inline]
182    pub fn push_null(&mut self) {
183        self.push_nulls(1)
184    }
185
186    /// Appends `n` null values. See [`push_null`](Self::push_null).
187    #[inline]
188    pub fn push_nulls(&mut self, n: usize) {
189        self.offsets.push_n(self.last_offset(), n);
190        self.validity.append_n(false, n);
191    }
192
193    /// Appends the same UTF-8 or binary scalar `n` times.
194    ///
195    /// # Errors
196    ///
197    /// Returns an error if `scalar` has a different dtype than the builder, or if the resulting
198    /// end offsets do not fit in `O`.
199    pub fn append_scalar_repeated(&mut self, scalar: &Scalar, n: usize) -> VortexResult<()> {
200        vortex_ensure!(
201            scalar.dtype() == &self.dtype,
202            "VarBinBuilder expected scalar with dtype {}, got {}",
203            self.dtype,
204            scalar.dtype()
205        );
206        match &self.dtype {
207            DType::Utf8(_) => match scalar.as_utf8().value() {
208                Some(value) => self.append_n_values(value, n)?,
209                None => self.push_nulls(n),
210            },
211            DType::Binary(_) => match scalar.as_binary().value() {
212                Some(value) => self.append_n_values(value, n)?,
213                None => self.push_nulls(n),
214            },
215            dtype => vortex_bail!("VarBinBuilder cannot append scalar of dtype {dtype}"),
216        }
217        Ok(())
218    }
219
220    /// Appends values from one contiguous byte buffer described by relative end offsets.
221    ///
222    /// Each entry of `end_offsets` marks the end of one value relative to the start of `values`,
223    /// and there must be exactly one per entry in `validity`.
224    #[inline]
225    pub fn append_values<P>(
226        &mut self,
227        values: &[u8],
228        end_offsets: impl Iterator<Item = P>,
229        validity: &Mask,
230    ) -> VortexResult<()>
231    where
232        P: AsPrimitive<usize>,
233        usize: AsPrimitive<O>,
234    {
235        // Offsets are committed first: they are the only fallible part, and failing before the
236        // bytes are appended leaves the builder untouched.
237        self.extend_offsets(values.len(), validity.len(), end_offsets)?;
238        self.data.extend_from_slice(values);
239        self.append_validity(validity);
240        Ok(())
241    }
242
243    /// Appends values whose bytes `decode` writes straight into the builder's byte storage.
244    ///
245    /// `decode` is handed at least `num_bytes + slack` bytes of uninitialized storage and returns
246    /// the number of bytes it initialized, which must be exactly `num_bytes`. Each entry of
247    /// `lengths` is the byte length of one value — including nulls, which are zero-length — so
248    /// there must be one per entry in `validity`.
249    ///
250    /// `slack` is spare headroom past the values themselves. It exists so that a decoder which
251    /// stores in wide fixed-size chunks can keep using them through the final value instead of
252    /// dropping into a byte-at-a-time tail; a decoder must still never write past the slice it is
253    /// handed, so `0` is correct for one that is already exact.
254    ///
255    /// Decoding in place saves staging the decoded heap in a temporary buffer and copying it in.
256    ///
257    /// `decode` is taken as a `dyn` reference so a caller that resolves the `lengths` type with
258    /// [`match_each_integer_ptype!`](crate::match_each_integer_ptype) can build the closure once
259    /// outside that match rather than inlining a whole decoder into each of its arms.
260    ///
261    /// # Errors
262    ///
263    /// Returns an error, leaving the builder unchanged, if `num_bytes` would push an offset past
264    /// what `O` can hold — checked before `decode` runs — or if `decode` reports a different byte
265    /// count than `num_bytes`, or if `lengths` does not describe those bytes.
266    ///
267    /// # Safety
268    ///
269    /// `decode` must initialize the first `n` bytes of the slice it is passed, where `n` is the
270    /// value it returns. Those bytes are published as initialized without ever being read first,
271    /// so a `decode` that over-reports leaves the builder holding uninitialized memory.
272    pub unsafe fn append_decoded<P>(
273        &mut self,
274        num_bytes: usize,
275        slack: usize,
276        lengths: &[P],
277        validity: &Mask,
278        decode: &mut dyn FnMut(&mut [MaybeUninit<u8>]) -> VortexResult<usize>,
279    ) -> VortexResult<()>
280    where
281        P: AsPrimitive<usize>,
282        usize: AsPrimitive<O>,
283    {
284        let Some(capacity) = num_bytes.checked_add(slack) else {
285            vortex_bail!("Decoded size overflow: {num_bytes} + {slack}");
286        };
287        // Checked before decoding: an `i32` builder that the decoded bytes would overflow should
288        // not pay for the decompression first.
289        self.check_offset_limit(self.data.len(), num_bytes)?;
290        self.data.reserve(capacity);
291
292        let data_len = self.data.len();
293        let written = decode(self.data.spare_capacity_mut())?;
294        vortex_ensure!(
295            written == num_bytes,
296            "Decoded {written} bytes, expected {num_bytes}"
297        );
298
299        // The decoded bytes live in spare capacity until `set_len` below, so an invalid `lengths`
300        // still leaves the builder unchanged.
301        self.extend_offsets(num_bytes, validity.len(), prefix_sums(lengths))?;
302
303        // SAFETY: `decode` reported initializing `written` spare bytes, and the caller guarantees
304        // that report is accurate; `written == num_bytes` was checked above.
305        unsafe { self.data.set_len(data_len + num_bytes) };
306        self.append_validity(validity);
307        Ok(())
308    }
309
310    /// Appends `validity.len()` values, taking each valid row's bytes from `value`.
311    ///
312    /// `value` is called once per valid row in ascending row order and the slices it returns must
313    /// total `num_bytes` bytes. Null rows consume no bytes and repeat the preceding offset.
314    ///
315    /// The valid rows are walked a `u64` word at a time rather than pulled one at a time through
316    /// an iterator, and both buffers are sized once up front, so the copy loop costs one offset
317    /// store plus one `memcpy` per value. A caller whose values only exist as a stream can still
318    /// use this by pulling from the stream inside `value`: the rows are visited in ascending
319    /// order.
320    pub fn append_valid_slices<'a, F>(
321        &mut self,
322        num_bytes: usize,
323        validity: &Mask,
324        value: F,
325    ) -> VortexResult<()>
326    where
327        F: FnMut(usize) -> &'a [u8],
328        usize: AsPrimitive<O>,
329    {
330        let data_start = self.data.len();
331        match self.gather_valid_slices(num_bytes, validity, value) {
332            Ok(()) => {
333                self.append_validity(validity);
334                Ok(())
335            }
336            Err(error) => {
337                // The offsets are never committed on failure, so only the copied bytes need to go.
338                self.data.truncate(data_start);
339                Err(error)
340            }
341        }
342    }
343
344    /// Appends a [`VarBinArray`], reusing its offsets instead of walking its values.
345    pub fn append_varbin(
346        &mut self,
347        array: ArrayView<'_, VarBin>,
348        ctx: &mut ExecutionCtx,
349    ) -> VortexResult<()>
350    where
351        usize: AsPrimitive<O>,
352    {
353        let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
354        let bytes: ByteBuffer = array.sliced_bytes();
355        let validity = array
356            .varbin_validity()
357            .execute_mask(array.as_ref().len(), ctx)?;
358        match_each_integer_ptype!(offsets.ptype(), |P| {
359            let offsets = offsets.as_slice::<P>();
360            let first: usize = offsets[0].as_();
361            self.append_values(
362                bytes.as_slice(),
363                // Wrapping keeps a corrupt offsets child from panicking here; `append_values`
364                // rejects the resulting non-monotonic offsets.
365                offsets[1..]
366                    .iter()
367                    .map(|offset| AsPrimitive::<usize>::as_(*offset).wrapping_sub(first)),
368                &validity,
369            )
370        })
371    }
372
373    /// Appends a [`VarBinViewArray`](crate::arrays::VarBinViewArray), compacting its values.
374    pub fn append_varbinview(
375        &mut self,
376        array: ArrayView<'_, VarBinView>,
377        ctx: &mut ExecutionCtx,
378    ) -> VortexResult<()>
379    where
380        usize: AsPrimitive<O>,
381    {
382        let len = array.as_ref().len();
383        let validity = array.varbinview_validity().execute_mask(len, ctx)?;
384
385        // Resolve the views slice and the data buffers once. Reading them per row costs a buffer
386        // handle clone, and the byte total needs only the fixed-width view headers.
387        let views = array.views();
388        let buffers = array
389            .data_buffers()
390            .iter()
391            .map(|buffer| buffer.as_host().as_slice())
392            .collect::<Vec<_>>();
393
394        let num_bytes = match validity.bit_buffer() {
395            AllOr::All => views.iter().map(|view| view.len() as usize).sum(),
396            AllOr::None => 0,
397            AllOr::Some(bits) => {
398                let mut total = 0;
399                bits.for_each_set_index(|index| total += views[index].len() as usize);
400                total
401            }
402        };
403
404        self.append_valid_slices(num_bytes, &validity, |index| views[index].bytes(&buffers))
405    }
406
407    /// Finishes the appended values into a [`VarBinArray`] and resets the builder.
408    #[allow(clippy::disallowed_methods)]
409    pub fn finish_into_varbin(&mut self) -> VarBinArray {
410        assert_eq!(
411            self.offsets.len() - 1,
412            self.validity.len(),
413            "The offset count must be one more than the validity length"
414        );
415
416        let allocator = self.offsets.allocator().clone();
417        let mut fresh_offsets = BufferMut::with_capacity_in(1, allocator.clone());
418        fresh_offsets.push(O::zero());
419        let offsets = PrimitiveArray::new(
420            std::mem::replace(&mut self.offsets, fresh_offsets).freeze(),
421            Validity::NonNullable,
422        );
423        let data = std::mem::replace(
424            &mut self.data,
425            BufferMut::empty_aligned_in(Alignment::of::<u8>(), allocator.clone()),
426        );
427        let nulls =
428            std::mem::replace(&mut self.validity, BitBufferMut::empty_in(allocator)).freeze();
429
430        let validity = Validity::from_bit_buffer(nulls, self.dtype.nullability());
431
432        // The builder adds offsets in monotonically increasing order. Store this statistic to
433        // prevent VarBinArray::validate from recomputing it after deserialization.
434        #[cfg(debug_assertions)]
435        {
436            let offsets_are_sorted = offsets
437                .statistics()
438                .compute_is_sorted(&mut legacy_session().create_execution_ctx())
439                .unwrap_or(false);
440            debug_assert!(offsets_are_sorted, "VarBinBuilder offsets must be sorted");
441        }
442        offsets
443            .statistics()
444            .set(Stat::IsSorted, Precision::Exact(true.into()));
445
446        // SAFETY: The builder maintains all invariants:
447        // - Offsets are monotonically increasing starting from 0 (guaranteed by builder logic).
448        // - Bytes buffer contains exactly the data referenced by offsets.
449        // - Validity matches the dtype nullability.
450        // - UTF-8 validity is ensured by the caller when using DType::Utf8.
451        unsafe {
452            VarBinArray::new_unchecked(
453                offsets.into_array(),
454                data.freeze(),
455                self.dtype.clone(),
456                validity,
457            )
458        }
459    }
460
461    #[inline]
462    fn last_offset(&self) -> O {
463        self.offsets[self.offsets.len() - 1]
464    }
465
466    /// Appends `value`'s bytes and its end offset, leaving the validity bits to the caller.
467    #[inline]
468    fn push_value(&mut self, value: &[u8]) {
469        self.offsets
470            .push(O::from(self.data.len() + value.len()).unwrap_or_else(|| {
471                vortex_panic!(
472                    "Failed to convert sum of {} and {} to offset of type {}",
473                    self.data.len(),
474                    value.len(),
475                    std::any::type_name::<O>()
476                )
477            }));
478        self.data.extend_from_slice(value);
479    }
480
481    fn append_validity(&mut self, validity: &Mask) {
482        match validity {
483            Mask::AllTrue(len) => self.validity.append_n(true, *len),
484            Mask::AllFalse(len) => self.validity.append_n(false, *len),
485            Mask::Values(values) => self.validity.append_buffer(values.bit_buffer()),
486        }
487    }
488
489    /// Appends `count` end offsets derived from `end_offsets`, shifted past the current data end.
490    ///
491    /// `end_offsets` must be monotonically non-decreasing and end at exactly `num_bytes`. Offsets
492    /// are written through the reserved spare capacity and committed with a single `set_len`, so a
493    /// rejected input leaves the buffer untouched and the caller can propagate the error.
494    fn extend_offsets<P>(
495        &mut self,
496        num_bytes: usize,
497        count: usize,
498        end_offsets: impl Iterator<Item = P>,
499    ) -> VortexResult<()>
500    where
501        P: AsPrimitive<usize>,
502        usize: AsPrimitive<O>,
503    {
504        let data_start = self.data.len();
505        let offsets_len = self.offsets.len();
506        self.check_offset_limit(data_start, num_bytes)?;
507        self.offsets.reserve(count);
508
509        // Writing into the spare capacity keeps the output cursor in a register: `push` rewrites
510        // the buffer length on every value, which the optimizer cannot hoist out of the loop.
511        let spare = &mut self.offsets.spare_capacity_mut()[..count];
512        let mut end_offsets = end_offsets;
513        let mut previous = 0usize;
514        for slot in spare.iter_mut() {
515            let Some(end) = end_offsets.next() else {
516                vortex_bail!("End offset count is less than the validity length {count}");
517            };
518            let end = end.as_();
519            vortex_ensure!(
520                end >= previous && end <= num_bytes,
521                "End offsets must be monotonically increasing within {num_bytes} bytes, \
522                 got {end} after {previous}"
523            );
524            slot.write((data_start + end).as_());
525            previous = end;
526        }
527        vortex_ensure!(
528            end_offsets.next().is_none(),
529            "End offset count exceeds the validity length {count}"
530        );
531        vortex_ensure!(
532            previous == num_bytes,
533            "Final end offset {previous} does not match the value byte count {num_bytes}"
534        );
535
536        // SAFETY: the loop initialized the first `count` spare slots.
537        unsafe { self.offsets.set_len(offsets_len + count) };
538        Ok(())
539    }
540
541    /// Copies each valid row's bytes from `value` into the byte storage and records the offsets.
542    /// See [`append_valid_slices`](Self::append_valid_slices); the validity bits are the caller's
543    /// job so that a failure here can be unwound.
544    fn gather_valid_slices<'a, F>(
545        &mut self,
546        num_bytes: usize,
547        validity: &Mask,
548        mut value: F,
549    ) -> VortexResult<()>
550    where
551        F: FnMut(usize) -> &'a [u8],
552        usize: AsPrimitive<O>,
553    {
554        let count = validity.len();
555        let data_start = self.data.len();
556        let offsets_len = self.offsets.len();
557        self.check_offset_limit(data_start, num_bytes)?;
558        self.offsets.reserve(count);
559        self.data.reserve(num_bytes);
560
561        // Disjoint field borrows: the offsets spare capacity stays valid while the byte buffer
562        // grows, since the two are separate allocations.
563        let Self { offsets, data, .. } = self;
564        let spare = &mut offsets.spare_capacity_mut()[..count];
565
566        match validity.bit_buffer() {
567            AllOr::All => {
568                for (row, slot) in spare.iter_mut().enumerate() {
569                    data.extend_from_slice(value(row));
570                    slot.write(data.len().as_());
571                }
572            }
573            AllOr::None => {
574                spare.fill(MaybeUninit::new(data_start.as_()));
575            }
576            AllOr::Some(bits) => {
577                let mut row = 0;
578                bits.for_each_set_index(|index| {
579                    // Null rows between the previous valid row and this one repeat its end offset.
580                    spare[row..index].fill(MaybeUninit::new(data.len().as_()));
581                    data.extend_from_slice(value(index));
582                    spare[index].write(data.len().as_());
583                    row = index + 1;
584                });
585                spare[row..].fill(MaybeUninit::new(data.len().as_()));
586            }
587        }
588
589        // A caller whose slices overrun `num_bytes` only grows the byte buffer past the reservation,
590        // so the overrun is caught here rather than per value. The offsets are still uncommitted, so
591        // rejecting it now leaves them untouched.
592        vortex_ensure!(
593            data.len() == data_start + num_bytes,
594            "Value slices total {} bytes, expected {num_bytes}",
595            data.len() - data_start
596        );
597
598        // SAFETY: every branch above initialized all `count` spare slots.
599        unsafe { self.offsets.set_len(offsets_len + count) };
600        Ok(())
601    }
602
603    /// Checks that an offset past `num_bytes` more bytes is representable as an `O`.
604    fn check_offset_limit(&self, data_start: usize, num_bytes: usize) -> VortexResult<()> {
605        let Some(limit) = data_start.checked_add(num_bytes) else {
606            vortex_bail!("Byte offset overflow: {data_start} + {num_bytes}");
607        };
608        vortex_ensure!(
609            u64::try_from(limit).is_ok_and(|limit| limit <= O::max_value_as_u64()),
610            "Byte offset {limit} does not fit in {}",
611            std::any::type_name::<O>()
612        );
613        Ok(())
614    }
615}
616
617impl<O: OffsetBuilderPType> ArrayBuilder for VarBinBuilder<O> {
618    fn as_any(&self) -> &dyn Any {
619        self
620    }
621
622    fn as_any_mut(&mut self) -> &mut dyn Any {
623        self
624    }
625
626    fn dtype(&self) -> &DType {
627        &self.dtype
628    }
629
630    fn len(&self) -> usize {
631        self.validity.len()
632    }
633
634    fn append_zeros(&mut self, n: usize) {
635        self.offsets.push_n(self.last_offset(), n);
636        self.validity.append_n(true, n);
637    }
638
639    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
640        self.push_nulls(n);
641    }
642
643    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
644        self.append_scalar_repeated(scalar, 1)
645    }
646
647    fn reserve_exact(&mut self, additional: usize) {
648        self.offsets.reserve(additional);
649        self.validity.reserve(additional);
650    }
651
652    fn finish(&mut self) -> ArrayRef {
653        self.finish_into_varbin().into_array()
654    }
655
656    fn finish_into_canonical(&mut self, ctx: &mut ExecutionCtx) -> Canonical {
657        self.finish()
658            .execute::<Canonical>(ctx)
659            .vortex_expect("varbin builder should canonicalize")
660    }
661}
662
663/// Recovers a concrete [`VarBinBuilder`] from a `&mut dyn ArrayBuilder`.
664///
665/// Evaluates to `Some($body)` with `$typed` bound to the `&mut VarBinBuilder<O>` when the builder
666/// is one, and `None` otherwise, so an encoding can fall through to its other output paths:
667///
668/// ```ignore
669/// if let Some(result) = match_each_varbin_builder!(builder, |builder| {
670///     append_my_encoding(array, builder, ctx)
671/// }) {
672///     return result;
673/// }
674/// ```
675#[macro_export]
676macro_rules! match_each_varbin_builder {
677    ($builder:expr, | $typed:ident | $body:expr) => {
678        $crate::__match_varbin_builder_widths!($builder, |$typed| $body, [u32, u64, i32, i64])
679    };
680}
681
682/// Expands `$body` once per listed offset width, guarded by a downcast. See
683/// [`match_each_varbin_builder!`].
684#[doc(hidden)]
685#[macro_export]
686macro_rules! __match_varbin_builder_widths {
687    ($builder:expr, | $typed:ident | $body:expr, [$($width:ty),+ $(,)?]) => {{
688        let __varbin_builder: &mut dyn $crate::builders::ArrayBuilder = $builder;
689        $crate::__match_varbin_builder_arms!(__varbin_builder, |$typed| $body, [$($width),+])
690    }};
691}
692
693/// The `if`/`else if` chain behind [`__match_varbin_builder_widths!`], one arm per width.
694#[doc(hidden)]
695#[macro_export]
696macro_rules! __match_varbin_builder_arms {
697    ($builder:expr, | $typed:ident | $body:expr, []) => {
698        None
699    };
700    ($builder:expr, | $typed:ident | $body:expr, [$head:ty $(, $tail:ty)*]) => {
701        if $builder
702            .as_any()
703            .is::<$crate::builders::VarBinBuilder<$head>>()
704        {
705            let $typed = match $builder
706                .as_any_mut()
707                .downcast_mut::<$crate::builders::VarBinBuilder<$head>>()
708            {
709                Some(typed) => typed,
710                None => unreachable!("builder type checked above"),
711            };
712            Some($body)
713        } else {
714            $crate::__match_varbin_builder_arms!($builder, |$typed| $body, [$($tail),*])
715        }
716    };
717}
718
719/// Running totals of `lengths`, wrapping so a corrupt lengths child is rejected rather than
720/// panicking; [`VarBinBuilder::extend_offsets`] catches the resulting non-monotonic offsets.
721#[inline]
722fn prefix_sums<P: AsPrimitive<usize>>(lengths: &[P]) -> impl Iterator<Item = usize> {
723    lengths.iter().scan(0usize, |end, length| {
724        *end = end.wrapping_add(length.as_());
725        Some(*end)
726    })
727}
728
729#[cfg(test)]
730mod tests {
731    use std::mem::MaybeUninit;
732
733    use rstest::rstest;
734    use vortex_error::VortexResult;
735    use vortex_mask::Mask;
736
737    use crate::IntoArray;
738    use crate::VortexSessionExecute;
739    use crate::array_session;
740    use crate::arrays::ChunkedArray;
741    use crate::arrays::ConstantArray;
742    use crate::arrays::VarBinArray;
743    use crate::arrays::VarBinViewArray;
744    use crate::arrays::varbin::VarBinArraySlotsExt;
745    use crate::arrays::varbin::builder::VarBinBuilder;
746    use crate::assert_arrays_eq;
747    use crate::builders::ArrayBuilder;
748    use crate::dtype::DType;
749    use crate::dtype::Nullability::Nullable;
750    use crate::expr::stats::Precision;
751    use crate::expr::stats::Stat;
752    use crate::expr::stats::StatsProviderExt;
753    use crate::scalar::Scalar;
754
755    #[test]
756    fn test_builder() {
757        let mut builder = VarBinBuilder::<i32>::with_capacity_in(
758            DType::Utf8(Nullable),
759            0,
760            vortex_buffer::BufferAllocatorRef::static_ref(),
761        );
762        builder.append(Some(b"hello"));
763        builder.append(None);
764        builder.append(Some(b"world"));
765        let array = builder.finish_into_varbin();
766
767        assert_eq!(array.len(), 3);
768        assert_eq!(array.dtype().nullability(), Nullable);
769        assert_eq!(
770            array
771                .execute_scalar(0, &mut array_session().create_execution_ctx())
772                .unwrap(),
773            Scalar::utf8("hello".to_string(), Nullable)
774        );
775        assert!(
776            array
777                .execute_scalar(1, &mut array_session().create_execution_ctx())
778                .unwrap()
779                .is_null()
780        );
781    }
782
783    #[rstest]
784    #[case(false)]
785    #[case(true)]
786    fn test_append_varbin_to_builder(#[case] large_offsets: bool) -> VortexResult<()> {
787        let source = VarBinArray::from_iter(
788            [
789                Some("prefix"),
790                Some("hello"),
791                None,
792                Some("world"),
793                Some("suffix"),
794            ],
795            DType::Utf8(Nullable),
796        )
797        .into_array()
798        .slice(1..4)?;
799        let mut ctx = array_session().create_execution_ctx();
800
801        let actual = with_offsets(large_offsets, source.dtype().clone(), |builder| {
802            source.append_to_builder(builder, &mut ctx)
803        })?;
804
805        assert_arrays_eq!(actual, source, &mut ctx);
806        Ok(())
807    }
808
809    #[test]
810    fn append_n_values_offset_overflow_returns_error() {
811        let mut builder = VarBinBuilder::<i32>::new_in(
812            DType::Utf8(Nullable),
813            vortex_buffer::BufferAllocatorRef::static_ref(),
814        );
815
816        // The limit is checked before anything is reserved, so the huge byte total is free.
817        let result = builder.append_n_values(b"hello", i32::MAX as usize / 5 + 1);
818
819        assert!(result.is_err());
820        assert_eq!(builder.offsets.len(), 1);
821        assert!(builder.data.is_empty());
822        assert_eq!(builder.validity.len(), 0);
823    }
824
825    #[test]
826    fn append_values_rejects_a_short_offset_count() {
827        let mut builder = VarBinBuilder::<i32>::new_in(
828            DType::Utf8(Nullable),
829            vortex_buffer::BufferAllocatorRef::static_ref(),
830        );
831
832        let result = builder.append_values(b"ab", [1usize, 2].into_iter(), &Mask::new_true(3));
833
834        assert!(result.is_err());
835        assert_eq!(builder.offsets.len(), 1);
836        assert!(builder.data.is_empty());
837    }
838
839    #[test]
840    fn append_values_rejects_non_monotonic_offsets() {
841        let mut builder = VarBinBuilder::<i32>::new_in(
842            DType::Utf8(Nullable),
843            vortex_buffer::BufferAllocatorRef::static_ref(),
844        );
845
846        let result = builder.append_values(b"ab", [2usize, 1].into_iter(), &Mask::new_true(2));
847
848        assert!(result.is_err());
849        assert_eq!(builder.offsets.len(), 1);
850    }
851
852    #[test]
853    fn append_decoded_writes_into_the_builder_storage() -> VortexResult<()> {
854        let mut ctx = array_session().create_execution_ctx();
855        let mut builder = VarBinBuilder::<i32>::new_in(
856            DType::Utf8(Nullable),
857            vortex_buffer::BufferAllocatorRef::static_ref(),
858        );
859
860        // SAFETY: the closure initializes exactly the 6 bytes it reports.
861        unsafe {
862            builder.append_decoded(
863                6,
864                4,
865                &[3usize, 0, 3],
866                &Mask::from_iter([true, false, true]),
867                &mut |spare: &mut [MaybeUninit<u8>]| {
868                    for (slot, byte) in spare.iter_mut().zip(b"foobar") {
869                        slot.write(*byte);
870                    }
871                    Ok(6)
872                },
873            )?;
874        }
875
876        let expected =
877            VarBinViewArray::from_iter([Some("foo"), None, Some("bar")], DType::Utf8(Nullable));
878        assert_arrays_eq!(builder.finish_into_varbin(), expected, &mut ctx);
879        Ok(())
880    }
881
882    /// The offset width has to be checked before `decode` runs: an overflowing `i32` builder
883    /// should not pay for a full decompression first.
884    #[test]
885    fn append_decoded_rejects_an_offset_overflow_without_decoding() {
886        let mut builder = VarBinBuilder::<i32>::new_in(
887            DType::Utf8(Nullable),
888            vortex_buffer::BufferAllocatorRef::static_ref(),
889        );
890        let num_bytes = i32::MAX as usize + 1;
891        let mut decoded = false;
892
893        // SAFETY: the closure is never called, and reports only what it initializes if it were.
894        let result = unsafe {
895            builder.append_decoded(num_bytes, 0, &[num_bytes], &Mask::new_true(1), &mut |_| {
896                decoded = true;
897                Ok(0)
898            })
899        };
900
901        assert!(result.is_err());
902        assert!(!decoded, "decode ran before the offset width was checked");
903        assert_eq!(builder.offsets.len(), 1);
904        assert_eq!(builder.validity.len(), 0);
905    }
906
907    /// `append_scalar_repeated` reports an offset overflow rather than panicking, so a constant
908    /// column wider than the offset type errors like the bulk appends do.
909    #[test]
910    fn append_scalar_repeated_rejects_an_offset_overflow() {
911        let mut builder = VarBinBuilder::<i32>::new_in(
912            DType::Utf8(Nullable),
913            vortex_buffer::BufferAllocatorRef::static_ref(),
914        );
915
916        let result = builder
917            .append_scalar_repeated(&Scalar::utf8("hello", Nullable), i32::MAX as usize / 5 + 1);
918
919        assert!(result.is_err());
920        assert_eq!(builder.offsets.len(), 1);
921        assert!(builder.data.is_empty());
922        assert_eq!(builder.validity.len(), 0);
923    }
924
925    #[test]
926    fn append_decoded_rejects_a_short_decode() {
927        let mut builder = VarBinBuilder::<i32>::new_in(
928            DType::Utf8(Nullable),
929            vortex_buffer::BufferAllocatorRef::static_ref(),
930        );
931
932        // SAFETY: the closure initializes the 3 bytes it reports (none, and it reports 3 — but the
933        // length mismatch is rejected before anything is published).
934        let result = unsafe {
935            builder.append_decoded(6, 0, &[3usize, 3], &Mask::new_true(2), &mut |spare| {
936                spare[..3].fill(MaybeUninit::new(b'x'));
937                Ok(3)
938            })
939        };
940
941        assert!(result.is_err());
942        assert_eq!(builder.offsets.len(), 1);
943        assert_eq!(builder.validity.len(), 0);
944    }
945
946    /// Slices that overrun the declared byte count are rejected, and the builder is left exactly
947    /// as it was — the overrun bytes are copied before the total can be checked, so the unwind has
948    /// to put them back.
949    #[rstest]
950    #[case::all_valid(Mask::new_true(2))]
951    #[case::some_valid(Mask::from_iter([true, false, true]))]
952    fn append_valid_slices_rejects_a_byte_count_mismatch(#[case] validity: Mask) {
953        let mut builder = VarBinBuilder::<i32>::new_in(
954            DType::Utf8(Nullable),
955            vortex_buffer::BufferAllocatorRef::static_ref(),
956        );
957        let values = [b"foo".as_slice(), b"quux".as_slice()];
958
959        let mut next = 0;
960        let result = builder.append_valid_slices(6, &validity, |_| {
961            next += 1;
962            values[next - 1]
963        });
964
965        assert!(result.is_err());
966        assert_eq!(builder.offsets.len(), 1);
967        assert!(builder.data.is_empty());
968        assert_eq!(builder.validity.len(), 0);
969    }
970
971    #[test]
972    #[should_panic(expected = "The offset count must be one more than the validity length")]
973    fn finish_rejects_mismatched_validity() {
974        let mut builder = VarBinBuilder::<i32>::new_in(
975            DType::Utf8(Nullable),
976            vortex_buffer::BufferAllocatorRef::static_ref(),
977        );
978        builder.validity.append_true();
979        drop(builder.finish_into_varbin());
980    }
981
982    #[rstest]
983    #[case(false)]
984    #[case(true)]
985    fn test_array_builder_methods(#[case] large_offsets: bool) -> VortexResult<()> {
986        let mut ctx = array_session().create_execution_ctx();
987        let result = with_offsets(large_offsets, DType::Utf8(Nullable), |builder| {
988            builder.reserve_exact(3);
989            builder.append_zero();
990            builder.append_scalar(&Scalar::utf8("hello", Nullable))?;
991            builder.append_null();
992            assert_eq!(builder.len(), 3);
993            Ok(())
994        })?;
995
996        assert_eq!(
997            result.validity()?.execute_mask(3, &mut ctx)?,
998            Mask::from_iter([true, true, false])
999        );
1000        Ok(())
1001    }
1002
1003    #[rstest]
1004    #[case(false)]
1005    #[case(true)]
1006    fn test_append_varbinview_validity_to_builder(#[case] large_offsets: bool) -> VortexResult<()> {
1007        let long = "a value that does not fit inline";
1008        let all_null = VarBinViewArray::from_iter([None::<&str>, None], DType::Utf8(Nullable));
1009        let mixed =
1010            VarBinViewArray::from_iter([Some("hello"), None, Some(long)], DType::Utf8(Nullable));
1011        let expected = VarBinViewArray::from_iter(
1012            [None, None, Some("hello"), None, Some(long)],
1013            DType::Utf8(Nullable),
1014        );
1015        let mut ctx = array_session().create_execution_ctx();
1016
1017        let actual = with_offsets(large_offsets, expected.dtype().clone(), |builder| {
1018            all_null
1019                .clone()
1020                .into_array()
1021                .append_to_builder(builder, &mut ctx)?;
1022            mixed
1023                .clone()
1024                .into_array()
1025                .append_to_builder(builder, &mut ctx)
1026        })?;
1027
1028        assert_arrays_eq!(actual, expected, &mut ctx);
1029        Ok(())
1030    }
1031
1032    /// `match_each_varbin_builder!` covers every offset width a `VarBinBuilder` can be built with,
1033    /// so both canonical appends must reach their specialization for all of them. A macro that
1034    /// only matched the signed pair would send an unsigned builder down a downcast that assumes
1035    /// `VarBinViewBuilder` and panic.
1036    #[rstest]
1037    #[case::u32(VarBinBuilder::<u32>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1038    #[case::u64(VarBinBuilder::<u64>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1039    #[case::i32(VarBinBuilder::<i32>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1040    #[case::i64(VarBinBuilder::<i64>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1041    fn append_to_every_offset_width(#[case] mut builder: impl ArrayBuilder) -> VortexResult<()> {
1042        let mut ctx = array_session().create_execution_ctx();
1043        let long = "a string that is far too long to be inlined in a view";
1044        let values = [Some("hello"), None, Some(long), Some("")];
1045
1046        let view = VarBinViewArray::from_iter(values, DType::Utf8(Nullable)).into_array();
1047        let varbin = VarBinArray::from_iter(values, DType::Utf8(Nullable)).into_array();
1048        let constant = ConstantArray::new(Scalar::from("hello").into_nullable(), 2).into_array();
1049
1050        for chunk in [&view, &varbin, &constant] {
1051            chunk.append_to_builder(&mut builder, &mut ctx)?;
1052        }
1053
1054        let expected = ChunkedArray::try_new(vec![view, varbin, constant], DType::Utf8(Nullable))?;
1055        assert_arrays_eq!(builder.finish(), expected, &mut ctx);
1056        Ok(())
1057    }
1058
1059    #[test]
1060    fn offsets_have_is_sorted_stat() -> VortexResult<()> {
1061        let mut builder = VarBinBuilder::<i32>::with_capacity_in(
1062            DType::Utf8(Nullable),
1063            0,
1064            vortex_buffer::BufferAllocatorRef::static_ref(),
1065        );
1066        builder.append_value(b"aaa");
1067        builder.push_null();
1068        builder.append_value(b"bbb");
1069        let array = builder.finish_into_varbin();
1070
1071        let is_sorted = array
1072            .offsets()
1073            .statistics()
1074            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
1075        assert_eq!(is_sorted, Precision::Exact(true));
1076        Ok(())
1077    }
1078
1079    #[test]
1080    fn empty_builder_offsets_have_is_sorted_stat() -> VortexResult<()> {
1081        let mut builder = VarBinBuilder::<i32>::new_in(
1082            DType::Utf8(Nullable),
1083            vortex_buffer::BufferAllocatorRef::static_ref(),
1084        );
1085        let array = builder.finish_into_varbin();
1086
1087        let is_sorted = array
1088            .offsets()
1089            .statistics()
1090            .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
1091        assert_eq!(is_sorted, Precision::Exact(true));
1092        Ok(())
1093    }
1094
1095    /// Runs `f` against an `i32` or `i64` builder and returns the finished array.
1096    fn with_offsets(
1097        large_offsets: bool,
1098        dtype: DType,
1099        f: impl FnOnce(&mut dyn ArrayBuilder) -> VortexResult<()>,
1100    ) -> VortexResult<VarBinArray> {
1101        if large_offsets {
1102            let mut builder = VarBinBuilder::<i64>::with_capacity_in(
1103                dtype,
1104                8,
1105                vortex_buffer::BufferAllocatorRef::static_ref(),
1106            );
1107            f(&mut builder)?;
1108            Ok(builder.finish_into_varbin())
1109        } else {
1110            let mut builder = VarBinBuilder::<i32>::with_capacity_in(
1111                dtype,
1112                8,
1113                vortex_buffer::BufferAllocatorRef::static_ref(),
1114            );
1115            f(&mut builder)?;
1116            Ok(builder.finish_into_varbin())
1117        }
1118    }
1119}