Skip to main content

vortex_array/builders/
varbinview.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::ops::Range;
6use std::sync::Arc;
7
8use itertools::Itertools;
9use vortex_buffer::Alignment;
10use vortex_buffer::Buffer;
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_mask::Mask;
19use vortex_utils::aliases::hash_map::Entry;
20use vortex_utils::aliases::hash_map::HashMap;
21
22use crate::ArrayRef;
23use crate::ExecutionCtx;
24use crate::IntoArray;
25use crate::arrays::VarBinViewArray;
26use crate::arrays::varbinview::VarBinViewArrayExt;
27use crate::arrays::varbinview::build_views::BinaryView;
28use crate::arrays::varbinview::compact::BufferUtilization;
29use crate::builders::ArrayBuilder;
30use crate::builders::LazyBitBufferBuilder;
31use crate::canonical::Canonical;
32use crate::dtype::DType;
33use crate::scalar::Scalar;
34
35/// The builder for building a [`VarBinViewArray`].
36pub struct VarBinViewBuilder {
37    dtype: DType,
38    views_builder: BufferMut<BinaryView>,
39    nulls: LazyBitBufferBuilder,
40    completed: CompletedBuffers,
41    in_progress: Option<ByteBufferMut>,
42    growth_strategy: BufferGrowthStrategy,
43    compaction_threshold: f64,
44}
45
46impl VarBinViewBuilder {
47    pub fn with_capacity(dtype: DType, capacity: usize) -> Self {
48        Self::new(dtype, capacity, Default::default(), Default::default(), 0.0)
49    }
50
51    pub fn with_buffer_deduplication(dtype: DType, capacity: usize) -> Self {
52        Self::new(
53            dtype,
54            capacity,
55            CompletedBuffers::Deduplicated(Default::default()),
56            Default::default(),
57            0.0,
58        )
59    }
60
61    pub fn with_compaction(dtype: DType, capacity: usize, compaction_threshold: f64) -> Self {
62        Self::new(
63            dtype,
64            capacity,
65            Default::default(),
66            Default::default(),
67            compaction_threshold,
68        )
69    }
70
71    pub fn new(
72        dtype: DType,
73        capacity: usize,
74        completed: CompletedBuffers,
75        growth_strategy: BufferGrowthStrategy,
76        compaction_threshold: f64,
77    ) -> Self {
78        assert!(
79            matches!(dtype, DType::Utf8(_) | DType::Binary(_)),
80            "VarBinViewBuilder DType must be Utf8 or Binary."
81        );
82        Self {
83            views_builder: BufferMut::with_capacity_preferred_aligned(
84                capacity,
85                Alignment::of::<BinaryView>(),
86                None,
87            ),
88            nulls: LazyBitBufferBuilder::new(capacity),
89            completed,
90            in_progress: None,
91            dtype,
92            growth_strategy,
93            compaction_threshold,
94        }
95    }
96
97    fn append_value_view(&mut self, value: &[u8]) {
98        let length =
99            u32::try_from(value.len()).vortex_expect("cannot have a single string >2^32 in length");
100        if length <= 12 {
101            self.views_builder.push(BinaryView::make_view(value, 0, 0));
102            return;
103        }
104
105        let (buffer_idx, offset) = self.append_value_to_buffer(value);
106        let view = BinaryView::make_view(value, buffer_idx, offset);
107        self.views_builder.push(view);
108    }
109
110    /// Appends a value to the builder.
111    pub fn append_value<S: AsRef<[u8]>>(&mut self, value: S) {
112        self.append_value_view(value.as_ref());
113        self.nulls.append_non_null();
114    }
115
116    /// Appends `n` copies of `value` as non-null entries.
117    pub fn append_n_values<S: AsRef<[u8]>>(&mut self, value: S, n: usize) {
118        if n == 0 {
119            return;
120        }
121        let bytes = value.as_ref();
122        let view = if bytes.len() <= BinaryView::MAX_INLINED_SIZE {
123            BinaryView::make_view(bytes, 0, 0)
124        } else {
125            let (buffer_idx, offset) = self.append_value_to_buffer(bytes);
126            BinaryView::make_view(bytes, buffer_idx, offset)
127        };
128        self.views_builder.push_n(view, n);
129        self.nulls.append_n_non_nulls(n);
130    }
131
132    fn flush_in_progress(&mut self) {
133        let Some(block) = self.in_progress.take() else {
134            return;
135        };
136
137        assert!(block.len() < u32::MAX as usize, "Block too large");
138
139        let initial_len = self.completed.len();
140        self.completed.push(block.freeze());
141        assert_eq!(
142            self.completed.len(),
143            initial_len + 1,
144            "Invalid state, just completed block already exists"
145        );
146    }
147
148    fn init_in_progress(&mut self, min_len: usize) {
149        let next_buffer_size = self.growth_strategy.next_size() as usize;
150        let to_reserve = next_buffer_size.max(min_len);
151        self.in_progress = Some(ByteBufferMut::with_capacity_preferred_aligned(
152            to_reserve,
153            Alignment::of::<u8>(),
154            None,
155        ));
156    }
157
158    /// append a non inlined value to self.in_progress.
159    fn append_value_to_buffer(&mut self, value: &[u8]) -> (u32, u32) {
160        assert!(
161            value.len() > BinaryView::MAX_INLINED_SIZE,
162            "must inline small strings"
163        );
164
165        if let Some(in_progress) = &mut self.in_progress {
166            let required_cap = in_progress.len() + value.len();
167            if in_progress.capacity() < required_cap {
168                self.flush_in_progress();
169                self.init_in_progress(value.len());
170            }
171        } else {
172            self.init_in_progress(value.len())
173        };
174
175        let in_progress = self
176            .in_progress
177            .as_mut()
178            .vortex_expect("in_progress just set");
179
180        let buffer_idx = self.completed.len();
181        let offset = u32::try_from(in_progress.len()).vortex_expect("too many buffers");
182        in_progress.extend_from_slice(value);
183
184        (buffer_idx, offset)
185    }
186
187    pub fn completed_block_count(&self) -> u32 {
188        self.completed.len()
189    }
190
191    /// Returns true if a non-empty in-progress buffer is staged (and would
192    /// become a completed buffer on the next flush), false otherwise.
193    pub fn in_progress(&self) -> bool {
194        self.in_progress.is_some()
195    }
196
197    /// Pushes buffers and pre-adjusted views into the builder.
198    ///
199    /// The provided `buffers` contain sections of data from a `VarBinViewArray`, and the
200    /// `views` are `BinaryView`s that have already been adjusted to reference the correct buffer
201    /// indices and offsets for this builder. All views must point to valid sections within the
202    /// provided buffers, and the validity length must match the view length.
203    ///
204    /// # Warning
205    ///
206    /// This method does not check utilization of the given buffers. Callers must provide
207    /// buffers that are fully utilized by the given adjusted views.
208    ///
209    /// # Panics
210    ///
211    /// Panics if this builder deduplicates buffers and any of the given buffers already
212    /// exist in this builder.
213    pub fn push_buffer_and_adjusted_views(
214        &mut self,
215        buffers: &[ByteBuffer],
216        views: &Buffer<BinaryView>,
217        validity_mask: Mask,
218    ) {
219        self.flush_in_progress();
220
221        let expected_completed_len = self.completed.len() as usize + buffers.len();
222        self.completed.extend_from_slice_unchecked(buffers);
223        assert_eq!(
224            self.completed.len() as usize,
225            expected_completed_len,
226            "Some buffers already exist",
227        );
228        self.views_builder.extend_trusted(views.iter().copied());
229        self.push_only_validity_mask(&validity_mask);
230
231        debug_assert_eq!(self.nulls.len(), self.views_builder.len())
232    }
233
234    /// Finishes the builder directly into a [`VarBinViewArray`].
235    pub fn finish_into_varbinview(&mut self) -> VarBinViewArray {
236        self.flush_in_progress();
237        let buffers = std::mem::take(&mut self.completed);
238
239        assert_eq!(
240            self.views_builder.len(),
241            self.nulls.len(),
242            "View and validity length must match"
243        );
244
245        let validity = self.nulls.finish_with_nullability(self.dtype.nullability());
246
247        // SAFETY: the builder methods check safety at each step.
248        unsafe {
249            VarBinViewArray::new_unchecked(
250                std::mem::take(&mut self.views_builder).freeze(),
251                buffers.finish(),
252                self.dtype.clone(),
253                validity,
254            )
255        }
256    }
257
258    // Pushes a validity mask into the builder not affecting the views or buffers
259    fn push_only_validity_mask(&mut self, validity_mask: &Mask) {
260        self.nulls.append_validity_mask(validity_mask);
261    }
262
263    pub(crate) fn append_varbinview_array(
264        &mut self,
265        array: &VarBinViewArray,
266        ctx: &mut ExecutionCtx,
267    ) -> VortexResult<()> {
268        self.flush_in_progress();
269
270        let mask = array.varbinview_validity().execute_mask(array.len(), ctx)?;
271
272        self.push_only_validity_mask(&mask);
273
274        let view_adjustment =
275            self.completed
276                .extend_from_compaction(BuffersWithOffsets::from_array(
277                    array,
278                    self.compaction_threshold,
279                    ctx,
280                ));
281
282        match view_adjustment {
283            ViewAdjustment::Precomputed(adjustment) => self.views_builder.extend_trusted(
284                array
285                    .views()
286                    .iter()
287                    .map(|view| adjustment.adjust_view(view)),
288            ),
289            ViewAdjustment::Rewriting(adjustment) => match mask {
290                Mask::AllTrue(_) => {
291                    for (idx, &view) in array.views().iter().enumerate() {
292                        let new_view = self.push_view(view, &adjustment, array, idx);
293                        self.views_builder.push(new_view);
294                    }
295                }
296                Mask::AllFalse(_) => {
297                    self.views_builder
298                        .push_n(BinaryView::empty_view(), array.len());
299                }
300                Mask::Values(v) => {
301                    for (idx, (&view, is_valid)) in
302                        array.views().iter().zip(v.bit_buffer().iter()).enumerate()
303                    {
304                        let new_view = if !is_valid {
305                            BinaryView::empty_view()
306                        } else {
307                            self.push_view(view, &adjustment, array, idx)
308                        };
309                        self.views_builder.push(new_view);
310                    }
311                }
312            },
313        }
314
315        Ok(())
316    }
317}
318
319impl ArrayBuilder for VarBinViewBuilder {
320    fn as_any(&self) -> &dyn Any {
321        self
322    }
323
324    fn as_any_mut(&mut self) -> &mut dyn Any {
325        self
326    }
327
328    fn dtype(&self) -> &DType {
329        &self.dtype
330    }
331
332    fn len(&self) -> usize {
333        self.nulls.len()
334    }
335
336    fn append_zeros(&mut self, n: usize) {
337        self.views_builder.push_n(BinaryView::empty_view(), n);
338        self.nulls.append_n_non_nulls(n);
339    }
340
341    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
342        self.views_builder.push_n(BinaryView::empty_view(), n);
343        self.nulls.append_n_nulls(n);
344    }
345
346    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
347        vortex_ensure!(
348            scalar.dtype() == self.dtype(),
349            "VarBinViewBuilder expected scalar with dtype {}, got {}",
350            self.dtype(),
351            scalar.dtype()
352        );
353
354        match self.dtype() {
355            DType::Utf8(_) => match scalar.as_utf8().value() {
356                Some(value) => self.append_value(value),
357                None => self.append_null(),
358            },
359            DType::Binary(_) => match scalar.as_binary().value() {
360                Some(value) => self.append_value(value),
361                None => self.append_null(),
362            },
363            _ => vortex_bail!(
364                "VarBinViewBuilder can only handle Utf8 or Binary scalars, got {:?}",
365                scalar.dtype()
366            ),
367        }
368
369        Ok(())
370    }
371
372    fn reserve_exact(&mut self, additional: usize) {
373        self.views_builder.reserve(additional);
374        self.nulls.reserve_exact(additional);
375    }
376
377    unsafe fn set_validity_unchecked(&mut self, validity: Mask) {
378        self.nulls = LazyBitBufferBuilder::from_validity_mask(validity);
379    }
380
381    fn finish(&mut self) -> ArrayRef {
382        self.finish_into_varbinview().into_array()
383    }
384
385    fn finish_into_canonical(&mut self, _ctx: &mut ExecutionCtx) -> Canonical {
386        Canonical::VarBinView(self.finish_into_varbinview())
387    }
388}
389
390impl VarBinViewBuilder {
391    #[inline]
392    fn push_view(
393        &mut self,
394        view: BinaryView,
395        adjustment: &RewritingViewAdjustment,
396        array: &VarBinViewArray,
397        idx: usize,
398    ) -> BinaryView {
399        if view.is_inlined() {
400            view
401        } else if let Some(adjusted) = adjustment.adjust_view(&view) {
402            adjusted
403        } else {
404            let bytes = array.bytes_at(idx);
405            let (new_buf_idx, new_offset) = self.append_value_to_buffer(&bytes);
406            BinaryView::make_view(bytes.as_slice(), new_buf_idx, new_offset)
407        }
408    }
409}
410
411pub enum CompletedBuffers {
412    Default(Vec<ByteBuffer>),
413    Deduplicated(DeduplicatedBuffers),
414}
415
416impl Default for CompletedBuffers {
417    fn default() -> Self {
418        Self::Default(Vec::new())
419    }
420}
421
422// Self::push enforces len < u32::max
423#[expect(clippy::cast_possible_truncation)]
424impl CompletedBuffers {
425    fn len(&self) -> u32 {
426        match self {
427            Self::Default(buffers) => buffers.len() as u32,
428            Self::Deduplicated(buffers) => buffers.len(),
429        }
430    }
431
432    fn push(&mut self, block: ByteBuffer) -> u32 {
433        match self {
434            Self::Default(buffers) => {
435                assert!(buffers.len() < u32::MAX as usize, "Too many blocks");
436                buffers.push(block);
437                self.len()
438            }
439            Self::Deduplicated(buffers) => buffers.push(block),
440        }
441    }
442
443    /// Does not compact buffers, bypasses utilization checks.
444    fn extend_from_slice_unchecked(&mut self, buffers: &[ByteBuffer]) {
445        for buffer in buffers {
446            self.push(buffer.clone());
447        }
448    }
449
450    fn extend_from_compaction(&mut self, buffers: BuffersWithOffsets) -> ViewAdjustment {
451        match (self, buffers) {
452            (
453                Self::Default(completed_buffers),
454                BuffersWithOffsets::AllKept { buffers, offsets },
455            ) => {
456                let buffer_offset = completed_buffers.len() as u32;
457                completed_buffers.extend_from_slice(&buffers);
458                ViewAdjustment::shift(buffer_offset, offsets)
459            }
460            (
461                Self::Default(completed_buffers),
462                BuffersWithOffsets::SomeCompacted { buffers, offsets },
463            ) => {
464                let lookup = buffers
465                    .iter()
466                    .map(|maybe_buffer| {
467                        maybe_buffer.as_ref().map(|buffer| {
468                            completed_buffers.push(buffer.clone());
469                            completed_buffers.len() as u32 - 1
470                        })
471                    })
472                    .collect();
473                ViewAdjustment::rewriting(lookup, offsets)
474            }
475
476            (
477                Self::Deduplicated(completed_buffers),
478                BuffersWithOffsets::AllKept { buffers, offsets },
479            ) => {
480                let buffer_lookup = completed_buffers.extend_from_iter(buffers.iter().cloned());
481                ViewAdjustment::lookup(buffer_lookup, offsets)
482            }
483            (
484                Self::Deduplicated(completed_buffers),
485                BuffersWithOffsets::SomeCompacted { buffers, offsets },
486            ) => {
487                let buffer_lookup = completed_buffers.extend_from_option_slice(&buffers);
488                ViewAdjustment::rewriting(buffer_lookup, offsets)
489            }
490        }
491    }
492
493    fn finish(self) -> Arc<[ByteBuffer]> {
494        match self {
495            Self::Default(buffers) => Arc::from(buffers),
496            Self::Deduplicated(buffers) => buffers.finish(),
497        }
498    }
499}
500
501#[derive(Default)]
502pub struct DeduplicatedBuffers {
503    buffers: Vec<ByteBuffer>,
504    buffer_to_idx: HashMap<BufferId, u32>,
505}
506
507impl DeduplicatedBuffers {
508    // Self::push enforces len < u32::max
509    #[expect(clippy::cast_possible_truncation)]
510    fn len(&self) -> u32 {
511        self.buffers.len() as u32
512    }
513
514    /// Push a new block if not seen before. Returns the idx of the block.
515    pub(crate) fn push(&mut self, block: ByteBuffer) -> u32 {
516        assert!(self.buffers.len() < u32::MAX as usize, "Too many blocks");
517
518        let initial_len = self.len();
519        let id = BufferId::from(&block);
520        match self.buffer_to_idx.entry(id) {
521            Entry::Occupied(idx) => *idx.get(),
522            Entry::Vacant(entry) => {
523                let idx = initial_len;
524                entry.insert(idx);
525                self.buffers.push(block);
526                idx
527            }
528        }
529    }
530
531    pub(crate) fn extend_from_option_slice(
532        &mut self,
533        buffers: &[Option<ByteBuffer>],
534    ) -> Vec<Option<u32>> {
535        buffers
536            .iter()
537            .map(|buffer| buffer.as_ref().map(|buf| self.push(buf.clone())))
538            .collect()
539    }
540
541    pub(crate) fn extend_from_iter(
542        &mut self,
543        buffers: impl Iterator<Item = ByteBuffer>,
544    ) -> Vec<u32> {
545        buffers.map(|buffer| self.push(buffer)).collect()
546    }
547
548    pub(crate) fn finish(self) -> Arc<[ByteBuffer]> {
549        Arc::from(self.buffers)
550    }
551}
552
553#[derive(PartialEq, Eq, Hash)]
554struct BufferId {
555    // *const u8 stored as usize for `Send`
556    ptr: usize,
557    len: usize,
558}
559
560impl BufferId {
561    fn from(buffer: &ByteBuffer) -> Self {
562        let slice = buffer.as_slice();
563        Self {
564            ptr: slice.as_ptr() as usize,
565            len: slice.len(),
566        }
567    }
568}
569
570#[derive(Debug, Clone)]
571pub enum BufferGrowthStrategy {
572    /// Use a fixed buffer size for all allocations.
573    Fixed { size: u32 },
574    /// Use exponential growth starting from initial_size, doubling until max_size.
575    Exponential { current_size: u32, max_size: u32 },
576}
577
578impl Default for BufferGrowthStrategy {
579    fn default() -> Self {
580        Self::Exponential {
581            current_size: 4 * 1024,    // 4KB starting size
582            max_size: 2 * 1024 * 1024, // 2MB max size
583        }
584    }
585}
586
587impl BufferGrowthStrategy {
588    pub fn fixed(size: u32) -> Self {
589        Self::Fixed { size }
590    }
591
592    pub fn exponential(initial_size: u32, max_size: u32) -> Self {
593        Self::Exponential {
594            current_size: initial_size,
595            max_size,
596        }
597    }
598
599    /// Returns the next buffer size to allocate and updates internal state.
600    pub fn next_size(&mut self) -> u32 {
601        match self {
602            Self::Fixed { size } => *size,
603            Self::Exponential {
604                current_size,
605                max_size,
606            } => {
607                let result = *current_size;
608                if *current_size < *max_size {
609                    *current_size = current_size.saturating_mul(2).min(*max_size);
610                }
611                result
612            }
613        }
614    }
615}
616
617enum BuffersWithOffsets {
618    AllKept {
619        buffers: Arc<[ByteBuffer]>,
620        offsets: Option<Vec<u32>>,
621    },
622    SomeCompacted {
623        buffers: Vec<Option<ByteBuffer>>,
624        offsets: Option<Vec<u32>>,
625    },
626}
627
628impl BuffersWithOffsets {
629    pub fn from_array(
630        array: &VarBinViewArray,
631        compaction_threshold: f64,
632        ctx: &mut ExecutionCtx,
633    ) -> Self {
634        if compaction_threshold == 0.0 {
635            return Self::AllKept {
636                buffers: Arc::from(
637                    array
638                        .data_buffers()
639                        .iter()
640                        .cloned()
641                        .map(|b| b.unwrap_host())
642                        .collect_vec(),
643                ),
644                offsets: None,
645            };
646        }
647
648        let buffer_utilizations = array
649            .buffer_utilizations(ctx)
650            .vortex_expect("buffer_utilizations in BuffersWithOffsets::from_array");
651        let mut has_rewrite = false;
652        let mut has_nonzero_offset = false;
653        for utilization in buffer_utilizations.iter() {
654            match compaction_strategy(utilization, compaction_threshold) {
655                CompactionStrategy::KeepFull => continue,
656                CompactionStrategy::Slice { .. } => has_nonzero_offset = true,
657                CompactionStrategy::Rewrite => has_rewrite = true,
658            }
659        }
660
661        let buffers_with_offsets_iter = buffer_utilizations
662            .iter()
663            .zip(array.data_buffers().iter())
664            .map(|(utilization, buffer)| {
665                match compaction_strategy(utilization, compaction_threshold) {
666                    CompactionStrategy::KeepFull => (Some(buffer.as_host().clone()), 0),
667                    CompactionStrategy::Slice { start, end } => (
668                        Some(buffer.as_host().slice(start as usize..end as usize)),
669                        start,
670                    ),
671                    CompactionStrategy::Rewrite => (None, 0),
672                }
673            });
674
675        match (has_rewrite, has_nonzero_offset) {
676            // keep all buffers
677            (false, false) => {
678                let buffers: Vec<_> = buffers_with_offsets_iter
679                    .map(|(b, _)| b.vortex_expect("already checked for rewrite"))
680                    .collect();
681                Self::AllKept {
682                    buffers: Arc::from(buffers),
683                    offsets: None,
684                }
685            }
686            // rewrite, all zero offsets
687            (true, false) => {
688                let buffers: Vec<_> = buffers_with_offsets_iter.map(|(b, _)| b).collect();
689                Self::SomeCompacted {
690                    buffers,
691                    offsets: None,
692                }
693            }
694            // keep all buffers, but some have offsets
695            (false, true) => {
696                let (buffers, offsets): (Vec<_>, _) = buffers_with_offsets_iter
697                    .map(|(buffer, offset)| {
698                        (buffer.vortex_expect("already checked for rewrite"), offset)
699                    })
700                    .collect();
701                Self::AllKept {
702                    buffers: Arc::from(buffers),
703                    offsets: Some(offsets),
704                }
705            }
706            // rewrite and some have offsets
707            (true, true) => {
708                let (buffers, offsets) = buffers_with_offsets_iter.collect();
709                Self::SomeCompacted {
710                    buffers,
711                    offsets: Some(offsets),
712                }
713            }
714        }
715    }
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Eq)]
719enum CompactionStrategy {
720    KeepFull,
721    /// Slice the buffer to [start, end) range
722    Slice {
723        start: u32,
724        end: u32,
725    },
726    /// Rewrite data into new compacted buffer
727    Rewrite,
728}
729
730fn compaction_strategy(
731    buffer_utilization: &BufferUtilization,
732    threshold: f64,
733) -> CompactionStrategy {
734    match buffer_utilization.overall_utilization() {
735        // rewrite empty or not used buffers TODO(os): maybe keep them
736        0.0 => CompactionStrategy::Rewrite,
737        utilised if utilised >= threshold => CompactionStrategy::KeepFull,
738        _ if buffer_utilization.range_utilization() >= threshold => {
739            let Range { start, end } = buffer_utilization.range();
740            CompactionStrategy::Slice { start, end }
741        }
742        _ => CompactionStrategy::Rewrite,
743    }
744}
745
746enum ViewAdjustment {
747    Precomputed(PrecomputedViewAdjustment),
748    Rewriting(RewritingViewAdjustment),
749}
750
751impl ViewAdjustment {
752    fn shift(buffer_offset: u32, offsets: Option<Vec<u32>>) -> Self {
753        Self::Precomputed(PrecomputedViewAdjustment::Shift {
754            buffer_offset,
755            offsets,
756        })
757    }
758
759    fn lookup(buffer_lookup: Vec<u32>, offsets: Option<Vec<u32>>) -> Self {
760        Self::Precomputed(PrecomputedViewAdjustment::Lookup {
761            buffer_lookup,
762            offsets,
763        })
764    }
765
766    fn rewriting(buffer_lookup: Vec<Option<u32>>, offsets: Option<Vec<u32>>) -> Self {
767        Self::Rewriting(RewritingViewAdjustment {
768            buffer_lookup,
769            offsets,
770        })
771    }
772}
773
774// Care when adding new variants or fields in this enum, it will mess with inlining if it gets too big
775enum PrecomputedViewAdjustment {
776    Shift {
777        buffer_offset: u32,
778        offsets: Option<Vec<u32>>,
779    },
780    Lookup {
781        buffer_lookup: Vec<u32>,
782        offsets: Option<Vec<u32>>,
783    },
784}
785
786impl PrecomputedViewAdjustment {
787    #[inline]
788    fn adjust_view(&self, view: &BinaryView) -> BinaryView {
789        if view.is_inlined() {
790            return *view;
791        }
792        let view_ref = view.as_view();
793        match self {
794            Self::Shift {
795                buffer_offset,
796                offsets,
797            } => {
798                let b_idx = view_ref.buffer_index;
799                let offset_shift = offsets
800                    .as_ref()
801                    .map(|o| o[b_idx as usize])
802                    .unwrap_or_default();
803
804                // If offset < offset_shift, this view was invalid and wasn't counted in buffer_utilizations.
805                // Return an empty view to match how invalid views are handled in the Rewriting path.
806                if view_ref.offset < offset_shift {
807                    return BinaryView::empty_view();
808                }
809
810                view_ref
811                    .with_buffer_and_offset(b_idx + buffer_offset, view_ref.offset - offset_shift)
812            }
813            Self::Lookup {
814                buffer_lookup,
815                offsets,
816            } => {
817                let b_idx = view_ref.buffer_index;
818                let buffer = buffer_lookup[b_idx as usize];
819                let offset_shift = offsets
820                    .as_ref()
821                    .map(|o| o[b_idx as usize])
822                    .unwrap_or_default();
823
824                // If offset < offset_shift, this view was invalid and wasn't counted in buffer_utilizations.
825                // Return an empty view to match how invalid views are handled in the Rewriting path.
826                if view_ref.offset < offset_shift {
827                    return BinaryView::empty_view();
828                }
829
830                view_ref.with_buffer_and_offset(buffer, view_ref.offset - offset_shift)
831            }
832        }
833        .into()
834    }
835}
836
837struct RewritingViewAdjustment {
838    buffer_lookup: Vec<Option<u32>>,
839    offsets: Option<Vec<u32>>,
840}
841
842impl RewritingViewAdjustment {
843    /// Can return None if this view can't be adjusted, because there is no precomputed lookup
844    /// for the current buffer.
845    #[inline]
846    fn adjust_view(&self, view: &BinaryView) -> Option<BinaryView> {
847        if view.is_inlined() {
848            return Some(*view);
849        }
850
851        let view_ref = view.as_view();
852        self.buffer_lookup[view_ref.buffer_index as usize].map(|buffer| {
853            let offset_shift = self
854                .offsets
855                .as_ref()
856                .map(|o| o[view_ref.buffer_index as usize])
857                .unwrap_or_default();
858            view_ref
859                .with_buffer_and_offset(buffer, view_ref.offset - offset_shift)
860                .into()
861        })
862    }
863}
864
865#[cfg(test)]
866mod tests {
867    use vortex_error::VortexResult;
868
869    use crate::IntoArray;
870    use crate::VortexSessionExecute;
871    use crate::array_session;
872    use crate::assert_arrays_eq;
873    use crate::builders::ArrayBuilder;
874    use crate::builders::VarBinViewBuilder;
875    use crate::builders::varbinview::VarBinViewArray;
876    use crate::dtype::DType;
877    use crate::dtype::Nullability;
878
879    #[test]
880    fn test_utf8_builder() {
881        let mut ctx = array_session().create_execution_ctx();
882        let mut builder = VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::Nullable), 10);
883
884        builder.append_value("Hello");
885        builder.append_null();
886        builder.append_value("World");
887
888        builder.append_nulls(2);
889
890        builder.append_zeros(2);
891        builder.append_value("test");
892
893        let actual = builder.finish();
894        let expected = <VarBinViewArray as FromIterator<_>>::from_iter([
895            Some("Hello"),
896            None,
897            Some("World"),
898            None,
899            None,
900            Some(""),
901            Some(""),
902            Some("test"),
903        ]);
904        assert_arrays_eq!(actual, expected, &mut ctx);
905    }
906
907    #[test]
908    fn test_utf8_builder_with_extend() {
909        let mut ctx = array_session().create_execution_ctx();
910        let array = {
911            let mut builder =
912                VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::Nullable), 10);
913            builder.append_null();
914            builder.append_value("Hello2");
915            builder.finish()
916        };
917        let mut builder = VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::Nullable), 10);
918
919        builder.append_value("Hello1");
920        array.append_to_builder(&mut builder, &mut ctx).unwrap();
921        builder.append_nulls(2);
922        builder.append_value("Hello3");
923
924        let actual = builder.finish_into_canonical(&mut ctx);
925        let expected = <VarBinViewArray as FromIterator<_>>::from_iter([
926            Some("Hello1"),
927            None,
928            Some("Hello2"),
929            None,
930            None,
931            Some("Hello3"),
932        ]);
933        assert_arrays_eq!(actual.into_array(), expected.into_array(), &mut ctx);
934    }
935
936    #[test]
937    fn test_buffer_deduplication() -> VortexResult<()> {
938        let array = {
939            let mut builder =
940                VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::Nullable), 10);
941            builder.append_value("This is a long string that should not be inlined");
942            builder.append_value("short string");
943            builder.finish_into_varbinview()
944        };
945
946        assert_eq!(array.data_buffers().len(), 1);
947        let mut builder =
948            VarBinViewBuilder::with_buffer_deduplication(DType::Utf8(Nullability::Nullable), 10);
949
950        let mut ctx = array_session().create_execution_ctx();
951
952        array.append_to_builder(&mut builder, &mut ctx)?;
953        assert_eq!(builder.completed_block_count(), 1);
954
955        array
956            .slice(1..2)?
957            .append_to_builder(&mut builder, &mut ctx)?;
958        array
959            .slice(0..1)?
960            .append_to_builder(&mut builder, &mut ctx)?;
961        assert_eq!(builder.completed_block_count(), 1);
962
963        let array2 = {
964            let mut builder =
965                VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::Nullable), 10);
966            builder.append_value("This is a long string that should not be inlined");
967            builder.finish_into_varbinview()
968        };
969
970        array2.append_to_builder(&mut builder, &mut ctx)?;
971        assert_eq!(builder.completed_block_count(), 2);
972
973        array
974            .slice(0..1)?
975            .append_to_builder(&mut builder, &mut ctx)?;
976        array2
977            .slice(0..1)?
978            .append_to_builder(&mut builder, &mut ctx)?;
979        assert_eq!(builder.completed_block_count(), 2);
980        Ok(())
981    }
982
983    #[test]
984    fn test_append_scalar() {
985        let mut ctx = array_session().create_execution_ctx();
986        use crate::scalar::Scalar;
987
988        // Test with Utf8 builder.
989        let mut utf8_builder =
990            VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::Nullable), 10);
991
992        // Test appending a valid utf8 value.
993        let utf8_scalar1 = Scalar::utf8("hello", Nullability::Nullable);
994        utf8_builder.append_scalar(&utf8_scalar1).unwrap();
995
996        // Test appending another value.
997        let utf8_scalar2 = Scalar::utf8("world", Nullability::Nullable);
998        utf8_builder.append_scalar(&utf8_scalar2).unwrap();
999
1000        // Test appending null value.
1001        let null_scalar = Scalar::null(DType::Utf8(Nullability::Nullable));
1002        utf8_builder.append_scalar(&null_scalar).unwrap();
1003
1004        let array = utf8_builder.finish();
1005        let expected =
1006            <VarBinViewArray as FromIterator<_>>::from_iter([Some("hello"), Some("world"), None]);
1007        assert_arrays_eq!(&array, &expected, &mut ctx);
1008
1009        // Test with Binary builder.
1010        let mut binary_builder =
1011            VarBinViewBuilder::with_capacity(DType::Binary(Nullability::Nullable), 10);
1012
1013        let binary_scalar = Scalar::binary(vec![1u8, 2, 3], Nullability::Nullable);
1014        binary_builder.append_scalar(&binary_scalar).unwrap();
1015
1016        let binary_null = Scalar::null(DType::Binary(Nullability::Nullable));
1017        binary_builder.append_scalar(&binary_null).unwrap();
1018
1019        let binary_array = binary_builder.finish();
1020        let expected =
1021            <VarBinViewArray as FromIterator<_>>::from_iter([Some(vec![1u8, 2, 3]), None]);
1022        assert_arrays_eq!(&binary_array, &expected, &mut ctx);
1023
1024        // Test wrong dtype error.
1025        let mut builder =
1026            VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::NonNullable), 10);
1027        let wrong_scalar = Scalar::from(42i32);
1028        assert!(builder.append_scalar(&wrong_scalar).is_err());
1029    }
1030
1031    #[test]
1032    fn test_buffer_growth_strategies() {
1033        use super::BufferGrowthStrategy;
1034
1035        // Test Fixed strategy
1036        let mut strategy = BufferGrowthStrategy::fixed(1024);
1037
1038        // Should always return the fixed size
1039        assert_eq!(strategy.next_size(), 1024);
1040        assert_eq!(strategy.next_size(), 1024);
1041        assert_eq!(strategy.next_size(), 1024);
1042
1043        // Test Exponential strategy
1044        let mut strategy = BufferGrowthStrategy::exponential(1024, 8192);
1045
1046        // Should double each time until hitting max_size
1047        assert_eq!(strategy.next_size(), 1024); // First: 1024
1048        assert_eq!(strategy.next_size(), 2048); // Second: 2048
1049        assert_eq!(strategy.next_size(), 4096); // Third: 4096
1050        assert_eq!(strategy.next_size(), 8192); // Fourth: 8192 (max)
1051        assert_eq!(strategy.next_size(), 8192); // Fifth: 8192 (capped)
1052    }
1053
1054    #[test]
1055    fn test_large_value_allocation() {
1056        use super::BufferGrowthStrategy;
1057        use super::VarBinViewBuilder;
1058
1059        let mut builder = VarBinViewBuilder::new(
1060            DType::Binary(Nullability::Nullable),
1061            10,
1062            Default::default(),
1063            BufferGrowthStrategy::exponential(1024, 4096),
1064            0.0,
1065        );
1066
1067        // Create a value larger than max_size
1068        let large_value = vec![0u8; 8192];
1069
1070        // Should successfully append the large value
1071        builder.append_value(&large_value);
1072
1073        let array = builder.finish_into_varbinview();
1074        assert_eq!(array.len(), 1);
1075
1076        // Verify the value was stored correctly
1077        let retrieved = array
1078            .execute_scalar(0, &mut array_session().create_execution_ctx())
1079            .unwrap()
1080            .as_binary()
1081            .value()
1082            .cloned()
1083            .unwrap();
1084        assert_eq!(retrieved.len(), 8192);
1085        assert_eq!(retrieved.as_slice(), &large_value);
1086    }
1087}