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