Skip to main content

vortex_array/arrays/varbinview/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::mem::size_of;
7use std::sync::Arc;
8
9use smallvec::smallvec;
10use vortex_buffer::Alignment;
11use vortex_buffer::Buffer;
12use vortex_buffer::BufferAllocatorRef;
13use vortex_buffer::ByteBuffer;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_bail;
17use vortex_error::vortex_ensure;
18use vortex_error::vortex_ensure_eq;
19use vortex_error::vortex_err;
20use vortex_error::vortex_panic;
21use vortex_mask::AllOr;
22use vortex_mask::Mask;
23
24use crate::ArrayRef;
25use crate::ArraySlots;
26use crate::ExecutionCtx;
27use crate::array::Array;
28use crate::array::ArrayParts;
29use crate::array::TypedArrayRef;
30use crate::array::child_to_validity;
31use crate::array::validity_to_child;
32use crate::array_slots;
33use crate::arrays::VarBinView;
34use crate::arrays::varbinview::BinaryView;
35use crate::arrays::varbinview::ResolvedViews;
36use crate::buffer::BufferHandle;
37use crate::builders::ArrayBuilder;
38use crate::builders::VarBinViewBuilder;
39use crate::dtype::DType;
40use crate::dtype::Nullability;
41use crate::validity::Validity;
42
43#[array_slots(VarBinView)]
44pub struct VarBinViewSlots {
45    /// The validity bitmap indicating which elements are non-null.
46    #[slot(0)]
47    pub validity: Option<ArrayRef>,
48}
49
50/// A variable-length binary view array that stores strings and binary data efficiently.
51///
52/// This mirrors the Apache Arrow StringView/BinaryView array encoding and provides
53/// an optimized representation for variable-length data with excellent performance
54/// characteristics for both short and long strings.
55///
56/// ## Data Layout
57///
58/// The array uses a hybrid storage approach with two main components:
59/// - **Views buffer**: Array of 16-byte `BinaryView` entries (one per logical element)
60/// - **Data buffers**: Shared backing storage for strings longer than 12 bytes
61///
62/// ## View Structure
63///
64/// Commonly referred to as "German Strings", each 16-byte view entry contains either:
65/// - **Inlined data**: For strings ≤ 12 bytes, the entire string is stored directly in the view
66/// - **Reference data**: For strings > 12 bytes, contains:
67///   - String length (4 bytes)
68///   - First 4 bytes of string as prefix (4 bytes)
69///   - Buffer index and offset (8 bytes total)
70///
71/// The following ASCII graphic is reproduced verbatim from the Arrow documentation:
72///
73/// ```text
74///                         ┌──────┬────────────────────────┐
75///                         │length│      string value      │
76///    Strings (len <= 12)  │      │    (padded with 0)     │
77///                         └──────┴────────────────────────┘
78///                          0    31                      127
79///
80///                         ┌───────┬───────┬───────┬───────┐
81///                         │length │prefix │  buf  │offset │
82///    Strings (len > 12)   │       │       │ index │       │
83///                         └───────┴───────┴───────┴───────┘
84///                          0    31       63      95    127
85/// ```
86///
87/// # Examples
88///
89/// ```
90/// use vortex_array::arrays::VarBinViewArray;
91/// use vortex_array::dtype::{DType, Nullability};
92/// use vortex_array::IntoArray;
93///
94/// // Create from an Iterator<Item = &str>
95/// let array = VarBinViewArray::from_iter_str([
96///         "inlined",
97///         "this string is outlined"
98/// ]);
99///
100/// assert_eq!(array.len(), 2);
101///
102/// // Access individual strings
103/// let first = array.bytes_at(0);
104/// assert_eq!(first.as_slice(), b"inlined"); // "short"
105///
106/// let second = array.bytes_at(1);
107/// assert_eq!(second.as_slice(), b"this string is outlined"); // Long string
108/// ```
109#[derive(Clone, Debug)]
110pub struct VarBinViewData {
111    pub(super) buffers: Arc<[BufferHandle]>,
112    pub(super) views: BufferHandle,
113}
114
115impl Display for VarBinViewData {
116    fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
117        Ok(())
118    }
119}
120
121pub struct VarBinViewDataParts {
122    pub dtype: DType,
123    pub buffers: Arc<[BufferHandle]>,
124    pub views: BufferHandle,
125    pub validity: Validity,
126}
127
128// Walk invalid (null) runs of "mask" as [start, end) ranges
129fn for_each_invalid_range(mask: &Mask, len: usize, mut f: impl FnMut(usize, usize)) {
130    match mask.bit_buffer() {
131        AllOr::All => {}
132        AllOr::None => f(0, len),
133        AllOr::Some(buffer) => {
134            let mut prev = 0;
135            for (start, end) in buffer.set_slices() {
136                if start > prev {
137                    f(prev, start);
138                }
139                prev = end;
140            }
141            if prev < len {
142                f(prev, len);
143            }
144        }
145    }
146}
147
148/// Calls `f` for each valid index of `mask`, and stops at the first error.
149fn try_for_each_valid(
150    mask: &Mask,
151    mut f: impl FnMut(usize) -> VortexResult<()>,
152) -> VortexResult<()> {
153    match mask.bit_buffer() {
154        AllOr::All => (0..mask.len()).try_for_each(f),
155        AllOr::None => Ok(()),
156        AllOr::Some(buffer) => buffer.set_indices().try_for_each(&mut f),
157    }
158}
159
160/// Returns `true` if all the inlined bytes of `view` are ASCII.
161fn is_inlined_ascii(view: &BinaryView) -> bool {
162    const HIGH_BITS: u128 = u128::from_le_bytes([0x80; 16]);
163
164    // The first 4 bytes hold the length, and the inlined bytes follow.
165    let data_bits = ((1u128 << (8 * view.len())) - 1) << 32;
166    view.as_u128() & data_bits & HIGH_BITS == 0
167}
168
169/// Returns `true` if `byte` starts a UTF-8 char, that is, it is not a continuation byte.
170fn is_char_boundary(byte: u8) -> bool {
171    // Continuation bytes have the form `0b10xx_xxxx`.
172    byte & 0b1100_0000 != 0b1000_0000
173}
174
175impl VarBinViewData {
176    fn dtype_parts(dtype: &DType) -> VortexResult<(bool, Nullability)> {
177        match dtype {
178            DType::Utf8(nullability) => Ok((true, *nullability)),
179            DType::Binary(nullability) => Ok((false, *nullability)),
180            _ => vortex_bail!(InvalidArgument: "invalid DType {dtype} for `VarBinViewArray`"),
181        }
182    }
183
184    /// Build the slots vector for this array.
185    pub(super) fn make_slots(validity: &Validity, len: usize) -> ArraySlots {
186        smallvec![validity_to_child(validity, len)]
187    }
188
189    /// Creates a new `VarBinViewArray`.
190    ///
191    /// # Panics
192    ///
193    /// Panics if the provided components do not satisfy the invariants documented
194    /// in `VarBinViewArray::new_unchecked`.
195    pub fn new(
196        views: Buffer<BinaryView>,
197        buffers: Arc<[ByteBuffer]>,
198        dtype: DType,
199        validity: Validity,
200        ctx: &mut ExecutionCtx,
201    ) -> Self {
202        Self::try_new(views, buffers, dtype, validity, ctx)
203            .vortex_expect("VarBinViewArray construction failed")
204    }
205
206    /// Creates a new `VarBinViewArray` with device or host memory.
207    ///
208    /// # Panics
209    ///
210    /// Panics if the provided components do not satisfy the invariants documented
211    /// in `VarBinViewArray::new_unchecked`.
212    pub fn new_handle(
213        views: BufferHandle,
214        buffers: Arc<[BufferHandle]>,
215        dtype: DType,
216        validity: Validity,
217    ) -> Self {
218        Self::try_new_handle(views, buffers, dtype, validity)
219            .vortex_expect("VarbinViewArray construction failed")
220    }
221
222    /// Constructs a new `VarBinViewArray`.
223    ///
224    /// See `VarBinViewArray::new_unchecked` for more information.
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if the provided components do not satisfy the invariants documented in
229    /// `VarBinViewArray::new_unchecked`.
230    pub fn try_new(
231        views: Buffer<BinaryView>,
232        buffers: Arc<[ByteBuffer]>,
233        dtype: DType,
234        validity: Validity,
235        ctx: &mut ExecutionCtx,
236    ) -> VortexResult<Self> {
237        let views = Self::validate_and_fix(views, &buffers, &dtype, &validity, ctx)?;
238
239        // SAFETY: validate ensures all invariants are met.
240        Ok(unsafe { Self::new_unchecked(views, buffers, dtype, validity) })
241    }
242
243    /// Constructs a new `VarBinViewArray`.
244    ///
245    /// See `VarBinViewArray::new_unchecked` for more information.
246    ///
247    /// # Errors
248    ///
249    /// Returns an error if the provided components do not satisfy the invariants documented in
250    /// `VarBinViewArray::new_unchecked`.
251    pub fn try_new_handle(
252        views: BufferHandle,
253        buffers: Arc<[BufferHandle]>,
254        dtype: DType,
255        validity: Validity,
256    ) -> VortexResult<Self> {
257        let views_nbytes = views.len();
258        vortex_ensure!(
259            views_nbytes.is_multiple_of(size_of::<BinaryView>()),
260            "Expected views buffer length ({views_nbytes}) to be a multiple of {}",
261            size_of::<BinaryView>()
262        );
263
264        // TODO(aduffy): device validation.
265        if let Some(host) = views.as_host_opt() {
266            vortex_ensure!(
267                host.is_aligned(Alignment::of::<BinaryView>()),
268                "Views on host must be 16 byte aligned"
269            );
270        }
271
272        // SAFETY: validate ensures all invariants are met.
273        Ok(unsafe { Self::new_handle_unchecked(views, buffers, dtype, validity) })
274    }
275
276    /// Creates a new `VarBinViewArray` without validation from these components:
277    ///
278    /// * `views` is a buffer of 16-byte view entries (one per logical element).
279    /// * `buffers` contains the backing storage for strings longer than 12 bytes.
280    /// * `dtype` specifies whether this contains UTF-8 strings or binary data.
281    /// * `validity` holds the null values.
282    ///
283    /// # Safety
284    ///
285    /// The caller must ensure all of the following invariants are satisfied:
286    ///
287    /// ## View Requirements
288    ///
289    /// - Views must be properly formatted 16-byte [`BinaryView`] entries.
290    /// - Inlined views (length ≤ 12) must have valid data in the first `length` bytes.
291    /// - Reference views (length > 12) must:
292    ///   - Have a valid buffer index < `buffers.len()`.
293    ///   - Have valid offsets that don't exceed the referenced buffer's bounds.
294    ///   - Have a 4-byte prefix that matches the actual data at the referenced location.
295    ///
296    /// ## Type Requirements
297    ///
298    /// - `dtype` must be either [`DType::Utf8`] or [`DType::Binary`].
299    /// - For [`DType::Utf8`], all string data (both inlined and referenced) must be valid UTF-8.
300    ///
301    /// ## Validity Requirements
302    ///
303    /// - The validity must have the same nullability as the dtype.
304    /// - If validity is an array, its length must match `views.len()`.
305    pub unsafe fn new_unchecked(
306        views: Buffer<BinaryView>,
307        buffers: Arc<[ByteBuffer]>,
308        dtype: DType,
309        validity: Validity,
310    ) -> Self {
311        let handles: Vec<BufferHandle> = buffers
312            .iter()
313            .cloned()
314            .map(BufferHandle::new_host)
315            .collect();
316
317        let handles = Arc::from(handles);
318        let view_handle = BufferHandle::new_host(views.into_byte_buffer());
319        unsafe { Self::new_handle_unchecked(view_handle, handles, dtype, validity) }
320    }
321
322    /// Construct a new array from `BufferHandle`s without validation.
323    ///
324    /// # Safety
325    ///
326    /// See documentation in `new_unchecked`.
327    pub unsafe fn new_handle_unchecked(
328        views: BufferHandle,
329        buffers: Arc<[BufferHandle]>,
330        dtype: DType,
331        _validity: Validity,
332    ) -> Self {
333        let _ =
334            Self::dtype_parts(&dtype).vortex_expect("VarBinViewArray dtype must be utf8 or binary");
335        Self { buffers, views }
336    }
337
338    /// Validates the components that would be used to create a `VarBinViewArray`.
339    ///
340    /// This function checks all the invariants required by `VarBinViewArray::new_unchecked`,
341    /// validating only the views at non-null slots.
342    pub fn validate(
343        views: &Buffer<BinaryView>,
344        buffers: &Arc<[ByteBuffer]>,
345        dtype: &DType,
346        validity: &Validity,
347        ctx: &mut ExecutionCtx,
348    ) -> VortexResult<()> {
349        let check_utf8 = Self::check_nullability(dtype, validity)?;
350        let mask = validity.execute_mask(views.len(), ctx)?;
351        Self::validate_views(views, &mask, buffers, check_utf8)
352    }
353
354    /// Validates components like validate() and replaces views at null slots to empty views
355    pub(crate) fn validate_and_fix(
356        views: Buffer<BinaryView>,
357        buffers: &Arc<[ByteBuffer]>,
358        dtype: &DType,
359        validity: &Validity,
360        ctx: &mut ExecutionCtx,
361    ) -> VortexResult<Buffer<BinaryView>> {
362        let check_utf8 = Self::check_nullability(dtype, validity)?;
363        let mask = validity.execute_mask(views.len(), ctx)?;
364
365        // With nulls and views we own, validate and replace in a single pass: a second pass over
366        // many short null runs costs more than a branch per view.
367        let views = match (mask.bit_buffer(), views.try_into_mut()) {
368            (AllOr::Some(bits), Ok(mut views)) => {
369                let buffers_utf8 = Self::buffers_utf8(&mask, buffers, check_utf8);
370                let buffers_utf8 = buffers_utf8.as_deref();
371                let empty = BinaryView::empty_view();
372                let slice = views.as_mut_slice();
373                for (idx, (view, valid)) in slice.iter_mut().zip(bits.iter()).enumerate() {
374                    if valid {
375                        Self::validate_view(idx, view, buffers, buffers_utf8)?;
376                    } else {
377                        *view = empty;
378                    }
379                }
380                return Ok(views.freeze());
381            }
382            (_, Ok(views)) => views.freeze(),
383            (_, Err(views)) => views,
384        };
385
386        Self::validate_views(&views, &mask, buffers, check_utf8)?;
387        Ok(Self::replace_null_views(views, &mask))
388    }
389
390    fn check_nullability(dtype: &DType, validity: &Validity) -> VortexResult<bool> {
391        let (is_utf8, nullability) = Self::dtype_parts(dtype)?;
392        vortex_ensure_eq!(
393            validity.nullability(),
394            nullability,
395            InvalidArgument: "validity nullability is incompatible with dtype nullability",
396        );
397        Ok(is_utf8)
398    }
399
400    /// Replaces the views at null slots with empty views. Copies the views only if a null slot
401    /// holds a non-empty view and the buffer is shared.
402    fn replace_null_views(views: Buffer<BinaryView>, mask: &Mask) -> Buffer<BinaryView> {
403        if mask.all_true() {
404            return views;
405        }
406
407        let empty = BinaryView::empty_view();
408        let len = views.len();
409
410        let mut views = match views.try_into_mut() {
411            Ok(views) => views,
412            Err(views) => {
413                let mut needs_replace = false;
414                for_each_invalid_range(mask, len, |start, end| {
415                    needs_replace |= views[start..end].iter().any(|view| *view != empty);
416                });
417                if !needs_replace {
418                    return views;
419                }
420                views.into_mut()
421            }
422        };
423
424        let slice = views.as_mut_slice();
425        for_each_invalid_range(mask, len, |start, end| slice[start..end].fill(empty));
426        views.freeze()
427    }
428
429    /// Validates the views at the valid slots of `mask`.
430    ///
431    /// A per-view UTF-8 check costs a function call per view, which dominates for short strings.
432    /// To avoid it, each data buffer is first checked once as a whole. If a buffer is valid UTF-8,
433    /// a view into it is valid UTF-8 if and only if it starts and ends on a char boundary.
434    ///
435    /// A buffer can hold bytes that no valid view references, for example after a slice, after a
436    /// filter, or at null views. So the whole-buffer check is only done while the total size of
437    /// the checked buffers stays within a fixed cost per valid view. The views into a buffer that
438    /// is not checked, or that is not valid UTF-8, are checked one by one.
439    fn validate_views(
440        views: &[BinaryView],
441        mask: &Mask,
442        buffers: &[ByteBuffer],
443        check_utf8: bool,
444    ) -> VortexResult<()> {
445        let buffers_utf8 = Self::buffers_utf8(mask, buffers, check_utf8);
446        let buffers_utf8 = buffers_utf8.as_deref();
447
448        try_for_each_valid(mask, |idx| {
449            Self::validate_view(idx, &views[idx], buffers, buffers_utf8)
450        })
451    }
452
453    /// The approximate cost of a per-view UTF-8 check, in bytes of a whole-buffer check.
454    const PER_VIEW_UTF8_COST: usize = 64;
455
456    /// Returns `None` if the UTF-8 check is off. Otherwise, returns for each buffer `true` if the
457    /// whole buffer was checked and is valid UTF-8.
458    ///
459    /// The budget only counts a fixed cost per valid view, not the bytes the views reference: for
460    /// long strings, the call per view is cheap next to the string, and checking a buffer larger
461    /// than the cache and then reading it again for the char boundaries is slower.
462    fn buffers_utf8(mask: &Mask, buffers: &[ByteBuffer], check_utf8: bool) -> Option<Vec<bool>> {
463        if !check_utf8 {
464            return None;
465        }
466
467        let mut budget = mask.true_count().saturating_mul(Self::PER_VIEW_UTF8_COST);
468
469        Some(
470            buffers
471                .iter()
472                .map(|buf| {
473                    if buf.len() > budget {
474                        return false;
475                    }
476
477                    budget -= buf.len();
478                    simdutf8::basic::from_utf8(buf).is_ok()
479                })
480                .collect(),
481        )
482    }
483
484    /// Checks the bounds, the prefix, and the UTF-8 of a view.
485    ///
486    /// `buffers_utf8` is `None` if the UTF-8 check is off. Otherwise, it tells which buffers are
487    /// valid UTF-8 as a whole.
488    fn validate_view(
489        idx: usize,
490        view: &BinaryView,
491        buffers: &[ByteBuffer],
492        buffers_utf8: Option<&[bool]>,
493    ) -> VortexResult<()> {
494        if view.is_inlined() {
495            if buffers_utf8.is_some() && !is_inlined_ascii(view) {
496                let bytes = view.as_inlined().value();
497                vortex_ensure!(
498                    simdutf8::basic::from_utf8(bytes).is_ok(),
499                    InvalidArgument: "view at index {idx}: inlined bytes failed utf-8 validation"
500                );
501            }
502            return Ok(());
503        }
504
505        let view = view.as_view();
506        let buf_index = view.buffer_index as usize;
507        let start_offset = view.offset as usize;
508        let end_offset = start_offset.saturating_add(view.size as usize);
509
510        let buf = buffers.get(buf_index).ok_or_else(||
511            vortex_err!(InvalidArgument: "view at index {idx} references invalid buffer: {buf_index} out of bounds for VarBinViewData with {} buffers",
512                buffers.len()))?;
513
514        vortex_ensure!(
515            start_offset < buf.len(),
516            InvalidArgument: "start offset {start_offset} out of bounds for buffer {buf_index} with size {}",
517            buf.len(),
518        );
519
520        vortex_ensure!(
521            end_offset <= buf.len(),
522            InvalidArgument: "end offset {end_offset} out of bounds for buffer {buf_index} with size {}",
523            buf.len(),
524        );
525
526        // Make sure the prefix data matches the buffer data.
527        vortex_ensure!(
528            view.prefix == buf[start_offset..start_offset + 4],
529            InvalidArgument: "VarBinView prefix does not match full string"
530        );
531
532        let Some(buffers_utf8) = buffers_utf8 else {
533            return Ok(());
534        };
535
536        let valid_utf8 = if buffers_utf8[buf_index] {
537            is_char_boundary(buf[start_offset])
538                && (end_offset == buf.len() || is_char_boundary(buf[end_offset]))
539        } else {
540            simdutf8::basic::from_utf8(&buf[start_offset..end_offset]).is_ok()
541        };
542
543        vortex_ensure!(
544            valid_utf8,
545            InvalidArgument: "view at index {idx}: outlined bytes fails utf-8 validation"
546        );
547
548        Ok(())
549    }
550
551    /// Returns the length of this array.
552    pub fn len(&self) -> usize {
553        self.views.len() / size_of::<BinaryView>()
554    }
555
556    /// Returns `true` if this array is empty.
557    pub fn is_empty(&self) -> bool {
558        self.len() == 0
559    }
560
561    /// Access to the primitive views buffer.
562    ///
563    /// Variable-sized binary view buffer contain a "view" child array, with 16-byte entries that
564    /// contain either a pointer into one of the array's owned `buffer`s OR an inlined copy of
565    /// the string (if the string has 12 bytes or fewer).
566    #[inline]
567    pub fn views(&self) -> &[BinaryView] {
568        let host_views = self.views.as_host();
569        let len = host_views.len() / size_of::<BinaryView>();
570
571        // SAFETY: data alignment is checked for host buffers on construction
572        unsafe { std::slice::from_raw_parts(host_views.as_ptr().cast(), len) }
573    }
574
575    /// Return the buffer handle backing the views.
576    pub fn views_handle(&self) -> &BufferHandle {
577        &self.views
578    }
579
580    /// Access value bytes at a given index
581    ///
582    /// Will return a `ByteBuffer` containing the data without performing a copy.
583    #[inline]
584    pub fn bytes_at(&self, index: usize) -> ByteBuffer {
585        let views = self.views();
586        let view = &views[index];
587        // Expect this to be the common case: strings > 12 bytes.
588        if !view.is_inlined() {
589            let view_ref = view.as_view();
590            self.buffer(view_ref.buffer_index as usize)
591                .slice(view_ref.as_range())
592        } else {
593            // Return access to the range of bytes around it.
594            self.views_handle()
595                .as_host()
596                .clone()
597                .into_byte_buffer()
598                .slice_ref(view.as_inlined().value())
599        }
600    }
601
602    /// Access one of the backing data buffers.
603    ///
604    /// # Panics
605    ///
606    /// This method panics if the provided index is out of bounds for the set of buffers provided
607    /// at construction time.
608    #[inline]
609    pub fn buffer(&self, idx: usize) -> &ByteBuffer {
610        if idx >= self.data_buffers().len() {
611            vortex_panic!(
612                "{idx} buffer index out of bounds, there are {} buffers",
613                self.data_buffers().len()
614            );
615        }
616        self.buffers[idx].as_host()
617    }
618
619    /// The underlying raw data buffers, not including the views buffer.
620    #[inline]
621    pub fn data_buffers(&self) -> &Arc<[BufferHandle]> {
622        &self.buffers
623    }
624
625    /// Accumulate an iterable set of values into our type here.
626    #[expect(
627        clippy::same_name_method,
628        reason = "intentionally named from_iter like Iterator::from_iter"
629    )]
630    pub fn from_iter<T: AsRef<[u8]>, I: IntoIterator<Item = Option<T>>>(
631        iter: I,
632        dtype: DType,
633    ) -> Self {
634        let iter = iter.into_iter();
635        let mut builder = VarBinViewBuilder::with_capacity_in(
636            dtype,
637            iter.size_hint().0,
638            BufferAllocatorRef::statically_allocated(),
639        );
640
641        for item in iter {
642            match item {
643                None => builder.append_null(),
644                Some(v) => builder.append_value(v),
645            }
646        }
647
648        builder.finish_into_varbinview().into_data()
649    }
650
651    pub fn from_iter_str<T: AsRef<str>, I: IntoIterator<Item = T>>(iter: I) -> Self {
652        let iter = iter.into_iter();
653        let mut builder = VarBinViewBuilder::with_capacity_in(
654            DType::Utf8(Nullability::NonNullable),
655            iter.size_hint().0,
656            BufferAllocatorRef::statically_allocated(),
657        );
658
659        for item in iter {
660            builder.append_value(item.as_ref());
661        }
662
663        builder.finish_into_varbinview().into_data()
664    }
665
666    pub fn from_iter_nullable_str<T: AsRef<str>, I: IntoIterator<Item = Option<T>>>(
667        iter: I,
668    ) -> Self {
669        let iter = iter.into_iter();
670        let mut builder = VarBinViewBuilder::with_capacity_in(
671            DType::Utf8(Nullability::Nullable),
672            iter.size_hint().0,
673            BufferAllocatorRef::statically_allocated(),
674        );
675
676        for item in iter {
677            match item {
678                None => builder.append_null(),
679                Some(v) => builder.append_value(v.as_ref()),
680            }
681        }
682
683        builder.finish_into_varbinview().into_data()
684    }
685
686    pub fn from_iter_bin<T: AsRef<[u8]>, I: IntoIterator<Item = T>>(iter: I) -> Self {
687        let iter = iter.into_iter();
688        let mut builder = VarBinViewBuilder::with_capacity_in(
689            DType::Binary(Nullability::NonNullable),
690            iter.size_hint().0,
691            BufferAllocatorRef::statically_allocated(),
692        );
693
694        for item in iter {
695            builder.append_value(item.as_ref());
696        }
697
698        builder.finish_into_varbinview().into_data()
699    }
700
701    pub fn from_iter_nullable_bin<T: AsRef<[u8]>, I: IntoIterator<Item = Option<T>>>(
702        iter: I,
703    ) -> Self {
704        let iter = iter.into_iter();
705        let mut builder = VarBinViewBuilder::with_capacity_in(
706            DType::Binary(Nullability::Nullable),
707            iter.size_hint().0,
708            BufferAllocatorRef::statically_allocated(),
709        );
710
711        for item in iter {
712            match item {
713                None => builder.append_null(),
714                Some(v) => builder.append_value(v.as_ref()),
715            }
716        }
717
718        builder.finish_into_varbinview().into_data()
719    }
720}
721
722pub trait VarBinViewArrayExt: TypedArrayRef<VarBinView> {
723    fn dtype_parts(&self) -> (bool, Nullability) {
724        match self.dtype() {
725            DType::Utf8(nullability) => (true, *nullability),
726            DType::Binary(nullability) => (false, *nullability),
727            _ => unreachable!("VarBinViewArrayExt requires a utf8 or binary dtype"),
728        }
729    }
730
731    fn varbinview_validity(&self) -> Validity {
732        child_to_validity(
733            self.slots()[VarBinViewSlots::VALIDITY].as_ref(),
734            self.dtype_parts().1,
735        )
736    }
737}
738impl<T: TypedArrayRef<VarBinView>> VarBinViewArrayExt for T {}
739
740impl Array<VarBinView> {
741    /// Resolve the data buffers of this array for per-row access.
742    pub fn resolved_views(&self) -> ResolvedViews<'_> {
743        ResolvedViews::new(self)
744    }
745
746    #[inline]
747    fn from_prevalidated_data(dtype: DType, data: VarBinViewData, slots: ArraySlots) -> Self {
748        let len = data.len();
749        unsafe { Array::from_parts_unchecked(ArrayParts::new(VarBinView, dtype, len, data, slots)) }
750    }
751
752    /// Construct a `VarBinViewArray` from an iterator of optional byte slices.
753    #[expect(
754        clippy::same_name_method,
755        reason = "intentionally named from_iter like Iterator::from_iter"
756    )]
757    pub fn from_iter<T: AsRef<[u8]>, I: IntoIterator<Item = Option<T>>>(
758        iter: I,
759        dtype: DType,
760    ) -> Self {
761        let iter = iter.into_iter();
762        let mut builder = VarBinViewBuilder::with_capacity_in(
763            dtype,
764            iter.size_hint().0,
765            BufferAllocatorRef::statically_allocated(),
766        );
767        for value in iter {
768            match value {
769                Some(value) => builder.append_value(value),
770                None => builder.append_null(),
771            }
772        }
773        builder.finish_into_varbinview()
774    }
775
776    pub fn from_iter_str<T: AsRef<str>, I: IntoIterator<Item = T>>(iter: I) -> Self {
777        let iter = iter.into_iter();
778        let mut builder = VarBinViewBuilder::with_capacity_in(
779            DType::Utf8(Nullability::NonNullable),
780            iter.size_hint().0,
781            BufferAllocatorRef::statically_allocated(),
782        );
783        for value in iter {
784            builder.append_value(value.as_ref());
785        }
786        builder.finish_into_varbinview()
787    }
788
789    pub fn from_iter_nullable_str<T: AsRef<str>, I: IntoIterator<Item = Option<T>>>(
790        iter: I,
791    ) -> Self {
792        let iter = iter.into_iter();
793        let mut builder = VarBinViewBuilder::with_capacity_in(
794            DType::Utf8(Nullability::Nullable),
795            iter.size_hint().0,
796            BufferAllocatorRef::statically_allocated(),
797        );
798        for value in iter {
799            match value {
800                Some(value) => builder.append_value(value.as_ref()),
801                None => builder.append_null(),
802            }
803        }
804        builder.finish_into_varbinview()
805    }
806
807    pub fn from_iter_bin<T: AsRef<[u8]>, I: IntoIterator<Item = T>>(iter: I) -> Self {
808        let iter = iter.into_iter();
809        let mut builder = VarBinViewBuilder::with_capacity_in(
810            DType::Binary(Nullability::NonNullable),
811            iter.size_hint().0,
812            BufferAllocatorRef::statically_allocated(),
813        );
814        for value in iter {
815            builder.append_value(value.as_ref());
816        }
817        builder.finish_into_varbinview()
818    }
819
820    pub fn from_iter_nullable_bin<T: AsRef<[u8]>, I: IntoIterator<Item = Option<T>>>(
821        iter: I,
822    ) -> Self {
823        let iter = iter.into_iter();
824        let mut builder = VarBinViewBuilder::with_capacity_in(
825            DType::Binary(Nullability::Nullable),
826            iter.size_hint().0,
827            BufferAllocatorRef::statically_allocated(),
828        );
829        for value in iter {
830            match value {
831                Some(value) => builder.append_value(value.as_ref()),
832                None => builder.append_null(),
833            }
834        }
835        builder.finish_into_varbinview()
836    }
837
838    /// Creates a new `VarBinViewArray`.
839    pub fn try_new(
840        views: Buffer<BinaryView>,
841        buffers: Arc<[ByteBuffer]>,
842        dtype: DType,
843        validity: Validity,
844        ctx: &mut ExecutionCtx,
845    ) -> VortexResult<Self> {
846        let data = VarBinViewData::try_new(views, buffers, dtype.clone(), validity.clone(), ctx)?;
847        let slots = VarBinViewData::make_slots(&validity, data.len());
848        Ok(Self::from_prevalidated_data(dtype, data, slots))
849    }
850
851    /// Creates a new `VarBinViewArray` without validation.
852    ///
853    /// # Safety
854    ///
855    /// See [`VarBinViewData::new_unchecked`].
856    pub unsafe fn new_unchecked(
857        views: Buffer<BinaryView>,
858        buffers: Arc<[ByteBuffer]>,
859        dtype: DType,
860        validity: Validity,
861    ) -> Self {
862        let data = unsafe {
863            VarBinViewData::new_unchecked(views, buffers, dtype.clone(), validity.clone())
864        };
865        let slots = VarBinViewData::make_slots(&validity, data.len());
866        Self::from_prevalidated_data(dtype, data, slots)
867    }
868
869    /// Creates a new `VarBinViewArray` with device or host memory.
870    pub fn new_handle(
871        views: BufferHandle,
872        buffers: Arc<[BufferHandle]>,
873        dtype: DType,
874        validity: Validity,
875    ) -> Self {
876        let data = VarBinViewData::new_handle(views, buffers, dtype.clone(), validity.clone());
877        let slots = VarBinViewData::make_slots(&validity, data.len());
878        Self::from_prevalidated_data(dtype, data, slots)
879    }
880
881    /// Construct a new array from `BufferHandle`s without validation.
882    ///
883    /// # Safety
884    ///
885    /// See [`VarBinViewData::new_handle_unchecked`].
886    pub unsafe fn new_handle_unchecked(
887        views: BufferHandle,
888        buffers: Arc<[BufferHandle]>,
889        dtype: DType,
890        validity: Validity,
891    ) -> Self {
892        let data = unsafe {
893            VarBinViewData::new_handle_unchecked(views, buffers, dtype.clone(), validity.clone())
894        };
895        let slots = VarBinViewData::make_slots(&validity, data.len());
896        Self::from_prevalidated_data(dtype, data, slots)
897    }
898
899    pub fn into_data_parts(self) -> VarBinViewDataParts {
900        let dtype = self.dtype().clone();
901        let validity = self.varbinview_validity();
902        let data = self.into_data();
903        VarBinViewDataParts {
904            dtype,
905            buffers: data.buffers,
906            views: data.views,
907            validity,
908        }
909    }
910}
911
912impl<'a> FromIterator<Option<&'a [u8]>> for VarBinViewData {
913    fn from_iter<T: IntoIterator<Item = Option<&'a [u8]>>>(iter: T) -> Self {
914        Self::from_iter_nullable_bin(iter)
915    }
916}
917
918impl FromIterator<Option<Vec<u8>>> for VarBinViewData {
919    fn from_iter<T: IntoIterator<Item = Option<Vec<u8>>>>(iter: T) -> Self {
920        Self::from_iter_nullable_bin(iter)
921    }
922}
923
924impl FromIterator<Option<String>> for VarBinViewData {
925    fn from_iter<T: IntoIterator<Item = Option<String>>>(iter: T) -> Self {
926        Self::from_iter_nullable_str(iter)
927    }
928}
929
930impl<'a> FromIterator<Option<&'a str>> for VarBinViewData {
931    fn from_iter<T: IntoIterator<Item = Option<&'a str>>>(iter: T) -> Self {
932        Self::from_iter_nullable_str(iter)
933    }
934}
935
936// --- FromIterator forwarding for Array<VarBinView> ---
937
938impl<'a> FromIterator<Option<&'a [u8]>> for Array<VarBinView> {
939    fn from_iter<T: IntoIterator<Item = Option<&'a [u8]>>>(iter: T) -> Self {
940        Self::from_iter(iter, DType::Binary(Nullability::Nullable))
941    }
942}
943
944impl FromIterator<Option<Vec<u8>>> for Array<VarBinView> {
945    fn from_iter<T: IntoIterator<Item = Option<Vec<u8>>>>(iter: T) -> Self {
946        Self::from_iter(iter, DType::Binary(Nullability::Nullable))
947    }
948}
949
950impl FromIterator<Option<String>> for Array<VarBinView> {
951    fn from_iter<T: IntoIterator<Item = Option<String>>>(iter: T) -> Self {
952        Self::from_iter_nullable_str(iter)
953    }
954}
955
956impl<'a> FromIterator<Option<&'a str>> for Array<VarBinView> {
957    fn from_iter<T: IntoIterator<Item = Option<&'a str>>>(iter: T) -> Self {
958        Self::from_iter_nullable_str(iter)
959    }
960}