1use std::any::Any;
5use std::mem::MaybeUninit;
6
7use num_traits::AsPrimitive;
8use vortex_buffer::Alignment;
9use vortex_buffer::BitBufferMut;
10use vortex_buffer::BufferAllocatorRef;
11use vortex_buffer::BufferMut;
12use vortex_buffer::ByteBuffer;
13use vortex_buffer::ByteBufferMut;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_bail;
17use vortex_error::vortex_ensure;
18use vortex_error::vortex_panic;
19use vortex_mask::AllOr;
20use vortex_mask::Mask;
21
22use crate::ArrayRef;
23use crate::ArrayView;
24use crate::Canonical;
25use crate::ExecutionCtx;
26use crate::IntoArray;
27#[cfg(debug_assertions)]
28use crate::VortexSessionExecute;
29use crate::arrays::PrimitiveArray;
30use crate::arrays::VarBin;
31use crate::arrays::VarBinArray;
32use crate::arrays::VarBinView;
33use crate::arrays::varbin::VarBinArrayExt;
34use crate::arrays::varbin::VarBinArraySlotsExt;
35use crate::arrays::varbinview::VarBinViewArrayExt;
36use crate::builders::ArrayBuilder;
37use crate::dtype::DType;
38use crate::dtype::OffsetBuilderPType;
39use crate::expr::stats::Precision;
40use crate::expr::stats::Stat;
41#[cfg(debug_assertions)]
42use crate::legacy_session;
43use crate::match_each_integer_ptype;
44use crate::scalar::Scalar;
45use crate::validity::Validity;
46
47pub struct VarBinBuilder<O: OffsetBuilderPType> {
60 dtype: DType,
61 offsets: BufferMut<O>,
62 data: ByteBufferMut,
63 validity: BitBufferMut,
64}
65
66impl<O: OffsetBuilderPType> VarBinBuilder<O> {
67 #[deprecated(note = "use `new_in` with an explicit allocator")]
69 pub fn new(dtype: DType) -> Self {
70 Self::new_in(dtype, BufferAllocatorRef::static_ref())
71 }
72
73 pub fn new_in(dtype: DType, allocator: &BufferAllocatorRef) -> Self {
75 Self::with_capacity_in(dtype, 0, allocator)
76 }
77
78 #[deprecated(note = "use `with_capacity_in` with an explicit allocator")]
80 pub fn with_capacity(dtype: DType, capacity: usize) -> Self {
81 Self::with_capacity_in(dtype, capacity, BufferAllocatorRef::static_ref())
82 }
83
84 pub fn with_capacity_in(dtype: DType, capacity: usize, allocator: &BufferAllocatorRef) -> Self {
86 assert!(
87 matches!(dtype, DType::Utf8(_) | DType::Binary(_)),
88 "VarBinBuilder dtype must be Utf8 or Binary, got {dtype}"
89 );
90 let mut offsets = BufferMut::with_capacity_in(capacity + 1, allocator.clone());
91 offsets.push(O::zero());
92 Self {
93 dtype,
94 offsets,
95 data: BufferMut::empty_aligned_in(Alignment::of::<u8>(), allocator.clone()),
96 validity: BitBufferMut::with_capacity_in(capacity, allocator.clone()),
97 }
98 }
99
100 #[deprecated(note = "use `with_capacity_bytes_in` with an explicit allocator")]
102 pub fn with_capacity_bytes(dtype: DType, capacity: usize, bytes: usize) -> Self {
103 Self::with_capacity_bytes_in(dtype, capacity, bytes, BufferAllocatorRef::static_ref())
104 }
105
106 pub fn with_capacity_bytes_in(
108 dtype: DType,
109 capacity: usize,
110 bytes: usize,
111 allocator: &BufferAllocatorRef,
112 ) -> Self {
113 let mut builder = Self::with_capacity_in(dtype, capacity, allocator);
114 builder.reserve_data(bytes);
115 builder
116 }
117
118 pub fn reserve_data(&mut self, additional: usize) {
124 self.data.reserve(additional);
125 }
126
127 #[inline]
134 pub fn append(&mut self, value: Option<&[u8]>) {
135 match value {
136 Some(v) => self.append_value(v),
137 None => self.push_null(),
138 }
139 }
140
141 #[inline]
149 pub fn append_value(&mut self, value: impl AsRef<[u8]>) {
150 self.push_value(value.as_ref());
151 self.validity.append_true();
152 }
153
154 pub fn append_n_values(&mut self, value: impl AsRef<[u8]>, n: usize) -> VortexResult<()> {
161 let value = value.as_ref();
162 let Some(num_bytes) = value.len().checked_mul(n) else {
163 vortex_bail!("Byte count overflow: {} values of {} bytes", n, value.len());
164 };
165 self.check_offset_limit(self.data.len(), num_bytes)?;
167 self.offsets.reserve(n);
168 self.data.reserve(num_bytes);
169 for _ in 0..n {
170 self.push_value(value);
171 }
172 self.validity.append_n(true, n);
173 Ok(())
174 }
175
176 #[inline]
182 pub fn push_null(&mut self) {
183 self.push_nulls(1)
184 }
185
186 #[inline]
188 pub fn push_nulls(&mut self, n: usize) {
189 self.offsets.push_n(self.last_offset(), n);
190 self.validity.append_n(false, n);
191 }
192
193 pub fn append_scalar_repeated(&mut self, scalar: &Scalar, n: usize) -> VortexResult<()> {
200 vortex_ensure!(
201 scalar.dtype() == &self.dtype,
202 "VarBinBuilder expected scalar with dtype {}, got {}",
203 self.dtype,
204 scalar.dtype()
205 );
206 match &self.dtype {
207 DType::Utf8(_) => match scalar.as_utf8().value() {
208 Some(value) => self.append_n_values(value, n)?,
209 None => self.push_nulls(n),
210 },
211 DType::Binary(_) => match scalar.as_binary().value() {
212 Some(value) => self.append_n_values(value, n)?,
213 None => self.push_nulls(n),
214 },
215 dtype => vortex_bail!("VarBinBuilder cannot append scalar of dtype {dtype}"),
216 }
217 Ok(())
218 }
219
220 #[inline]
225 pub fn append_values<P>(
226 &mut self,
227 values: &[u8],
228 end_offsets: impl Iterator<Item = P>,
229 validity: &Mask,
230 ) -> VortexResult<()>
231 where
232 P: AsPrimitive<usize>,
233 usize: AsPrimitive<O>,
234 {
235 self.extend_offsets(values.len(), validity.len(), end_offsets)?;
238 self.data.extend_from_slice(values);
239 self.append_validity(validity);
240 Ok(())
241 }
242
243 pub unsafe fn append_decoded<P>(
273 &mut self,
274 num_bytes: usize,
275 slack: usize,
276 lengths: &[P],
277 validity: &Mask,
278 decode: &mut dyn FnMut(&mut [MaybeUninit<u8>]) -> VortexResult<usize>,
279 ) -> VortexResult<()>
280 where
281 P: AsPrimitive<usize>,
282 usize: AsPrimitive<O>,
283 {
284 let Some(capacity) = num_bytes.checked_add(slack) else {
285 vortex_bail!("Decoded size overflow: {num_bytes} + {slack}");
286 };
287 self.check_offset_limit(self.data.len(), num_bytes)?;
290 self.data.reserve(capacity);
291
292 let data_len = self.data.len();
293 let written = decode(self.data.spare_capacity_mut())?;
294 vortex_ensure!(
295 written == num_bytes,
296 "Decoded {written} bytes, expected {num_bytes}"
297 );
298
299 self.extend_offsets(num_bytes, validity.len(), prefix_sums(lengths))?;
302
303 unsafe { self.data.set_len(data_len + num_bytes) };
306 self.append_validity(validity);
307 Ok(())
308 }
309
310 pub fn append_valid_slices<'a, F>(
321 &mut self,
322 num_bytes: usize,
323 validity: &Mask,
324 value: F,
325 ) -> VortexResult<()>
326 where
327 F: FnMut(usize) -> &'a [u8],
328 usize: AsPrimitive<O>,
329 {
330 let data_start = self.data.len();
331 match self.gather_valid_slices(num_bytes, validity, value) {
332 Ok(()) => {
333 self.append_validity(validity);
334 Ok(())
335 }
336 Err(error) => {
337 self.data.truncate(data_start);
339 Err(error)
340 }
341 }
342 }
343
344 pub fn append_varbin(
346 &mut self,
347 array: ArrayView<'_, VarBin>,
348 ctx: &mut ExecutionCtx,
349 ) -> VortexResult<()>
350 where
351 usize: AsPrimitive<O>,
352 {
353 let offsets = array.offsets().clone().execute::<PrimitiveArray>(ctx)?;
354 let bytes: ByteBuffer = array.sliced_bytes();
355 let validity = array
356 .varbin_validity()
357 .execute_mask(array.as_ref().len(), ctx)?;
358 match_each_integer_ptype!(offsets.ptype(), |P| {
359 let offsets = offsets.as_slice::<P>();
360 let first: usize = offsets[0].as_();
361 self.append_values(
362 bytes.as_slice(),
363 offsets[1..]
366 .iter()
367 .map(|offset| AsPrimitive::<usize>::as_(*offset).wrapping_sub(first)),
368 &validity,
369 )
370 })
371 }
372
373 pub fn append_varbinview(
375 &mut self,
376 array: ArrayView<'_, VarBinView>,
377 ctx: &mut ExecutionCtx,
378 ) -> VortexResult<()>
379 where
380 usize: AsPrimitive<O>,
381 {
382 let len = array.as_ref().len();
383 let validity = array.varbinview_validity().execute_mask(len, ctx)?;
384
385 let views = array.views();
388 let buffers = array
389 .data_buffers()
390 .iter()
391 .map(|buffer| buffer.as_host().as_slice())
392 .collect::<Vec<_>>();
393
394 let num_bytes = match validity.bit_buffer() {
395 AllOr::All => views.iter().map(|view| view.len() as usize).sum(),
396 AllOr::None => 0,
397 AllOr::Some(bits) => {
398 let mut total = 0;
399 bits.for_each_set_index(|index| total += views[index].len() as usize);
400 total
401 }
402 };
403
404 self.append_valid_slices(num_bytes, &validity, |index| views[index].bytes(&buffers))
405 }
406
407 #[allow(clippy::disallowed_methods)]
409 pub fn finish_into_varbin(&mut self) -> VarBinArray {
410 assert_eq!(
411 self.offsets.len() - 1,
412 self.validity.len(),
413 "The offset count must be one more than the validity length"
414 );
415
416 let allocator = self.offsets.allocator().clone();
417 let mut fresh_offsets = BufferMut::with_capacity_in(1, allocator.clone());
418 fresh_offsets.push(O::zero());
419 let offsets = PrimitiveArray::new(
420 std::mem::replace(&mut self.offsets, fresh_offsets).freeze(),
421 Validity::NonNullable,
422 );
423 let data = std::mem::replace(
424 &mut self.data,
425 BufferMut::empty_aligned_in(Alignment::of::<u8>(), allocator.clone()),
426 );
427 let nulls =
428 std::mem::replace(&mut self.validity, BitBufferMut::empty_in(allocator)).freeze();
429
430 let validity = Validity::from_bit_buffer(nulls, self.dtype.nullability());
431
432 #[cfg(debug_assertions)]
435 {
436 let offsets_are_sorted = offsets
437 .statistics()
438 .compute_is_sorted(&mut legacy_session().create_execution_ctx())
439 .unwrap_or(false);
440 debug_assert!(offsets_are_sorted, "VarBinBuilder offsets must be sorted");
441 }
442 offsets
443 .statistics()
444 .set(Stat::IsSorted, Precision::Exact(true.into()));
445
446 unsafe {
452 VarBinArray::new_unchecked(
453 offsets.into_array(),
454 data.freeze(),
455 self.dtype.clone(),
456 validity,
457 )
458 }
459 }
460
461 #[inline]
462 fn last_offset(&self) -> O {
463 self.offsets[self.offsets.len() - 1]
464 }
465
466 #[inline]
468 fn push_value(&mut self, value: &[u8]) {
469 self.offsets
470 .push(O::from(self.data.len() + value.len()).unwrap_or_else(|| {
471 vortex_panic!(
472 "Failed to convert sum of {} and {} to offset of type {}",
473 self.data.len(),
474 value.len(),
475 std::any::type_name::<O>()
476 )
477 }));
478 self.data.extend_from_slice(value);
479 }
480
481 fn append_validity(&mut self, validity: &Mask) {
482 match validity {
483 Mask::AllTrue(len) => self.validity.append_n(true, *len),
484 Mask::AllFalse(len) => self.validity.append_n(false, *len),
485 Mask::Values(values) => self.validity.append_buffer(values.bit_buffer()),
486 }
487 }
488
489 fn extend_offsets<P>(
495 &mut self,
496 num_bytes: usize,
497 count: usize,
498 end_offsets: impl Iterator<Item = P>,
499 ) -> VortexResult<()>
500 where
501 P: AsPrimitive<usize>,
502 usize: AsPrimitive<O>,
503 {
504 let data_start = self.data.len();
505 let offsets_len = self.offsets.len();
506 self.check_offset_limit(data_start, num_bytes)?;
507 self.offsets.reserve(count);
508
509 let spare = &mut self.offsets.spare_capacity_mut()[..count];
512 let mut end_offsets = end_offsets;
513 let mut previous = 0usize;
514 for slot in spare.iter_mut() {
515 let Some(end) = end_offsets.next() else {
516 vortex_bail!("End offset count is less than the validity length {count}");
517 };
518 let end = end.as_();
519 vortex_ensure!(
520 end >= previous && end <= num_bytes,
521 "End offsets must be monotonically increasing within {num_bytes} bytes, \
522 got {end} after {previous}"
523 );
524 slot.write((data_start + end).as_());
525 previous = end;
526 }
527 vortex_ensure!(
528 end_offsets.next().is_none(),
529 "End offset count exceeds the validity length {count}"
530 );
531 vortex_ensure!(
532 previous == num_bytes,
533 "Final end offset {previous} does not match the value byte count {num_bytes}"
534 );
535
536 unsafe { self.offsets.set_len(offsets_len + count) };
538 Ok(())
539 }
540
541 fn gather_valid_slices<'a, F>(
545 &mut self,
546 num_bytes: usize,
547 validity: &Mask,
548 mut value: F,
549 ) -> VortexResult<()>
550 where
551 F: FnMut(usize) -> &'a [u8],
552 usize: AsPrimitive<O>,
553 {
554 let count = validity.len();
555 let data_start = self.data.len();
556 let offsets_len = self.offsets.len();
557 self.check_offset_limit(data_start, num_bytes)?;
558 self.offsets.reserve(count);
559 self.data.reserve(num_bytes);
560
561 let Self { offsets, data, .. } = self;
564 let spare = &mut offsets.spare_capacity_mut()[..count];
565
566 match validity.bit_buffer() {
567 AllOr::All => {
568 for (row, slot) in spare.iter_mut().enumerate() {
569 data.extend_from_slice(value(row));
570 slot.write(data.len().as_());
571 }
572 }
573 AllOr::None => {
574 spare.fill(MaybeUninit::new(data_start.as_()));
575 }
576 AllOr::Some(bits) => {
577 let mut row = 0;
578 bits.for_each_set_index(|index| {
579 spare[row..index].fill(MaybeUninit::new(data.len().as_()));
581 data.extend_from_slice(value(index));
582 spare[index].write(data.len().as_());
583 row = index + 1;
584 });
585 spare[row..].fill(MaybeUninit::new(data.len().as_()));
586 }
587 }
588
589 vortex_ensure!(
593 data.len() == data_start + num_bytes,
594 "Value slices total {} bytes, expected {num_bytes}",
595 data.len() - data_start
596 );
597
598 unsafe { self.offsets.set_len(offsets_len + count) };
600 Ok(())
601 }
602
603 fn check_offset_limit(&self, data_start: usize, num_bytes: usize) -> VortexResult<()> {
605 let Some(limit) = data_start.checked_add(num_bytes) else {
606 vortex_bail!("Byte offset overflow: {data_start} + {num_bytes}");
607 };
608 vortex_ensure!(
609 u64::try_from(limit).is_ok_and(|limit| limit <= O::max_value_as_u64()),
610 "Byte offset {limit} does not fit in {}",
611 std::any::type_name::<O>()
612 );
613 Ok(())
614 }
615}
616
617impl<O: OffsetBuilderPType> ArrayBuilder for VarBinBuilder<O> {
618 fn as_any(&self) -> &dyn Any {
619 self
620 }
621
622 fn as_any_mut(&mut self) -> &mut dyn Any {
623 self
624 }
625
626 fn dtype(&self) -> &DType {
627 &self.dtype
628 }
629
630 fn len(&self) -> usize {
631 self.validity.len()
632 }
633
634 fn append_zeros(&mut self, n: usize) {
635 self.offsets.push_n(self.last_offset(), n);
636 self.validity.append_n(true, n);
637 }
638
639 unsafe fn append_nulls_unchecked(&mut self, n: usize) {
640 self.push_nulls(n);
641 }
642
643 fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
644 self.append_scalar_repeated(scalar, 1)
645 }
646
647 fn reserve_exact(&mut self, additional: usize) {
648 self.offsets.reserve(additional);
649 self.validity.reserve(additional);
650 }
651
652 fn finish(&mut self) -> ArrayRef {
653 self.finish_into_varbin().into_array()
654 }
655
656 fn finish_into_canonical(&mut self, ctx: &mut ExecutionCtx) -> Canonical {
657 self.finish()
658 .execute::<Canonical>(ctx)
659 .vortex_expect("varbin builder should canonicalize")
660 }
661}
662
663#[macro_export]
676macro_rules! match_each_varbin_builder {
677 ($builder:expr, | $typed:ident | $body:expr) => {
678 $crate::__match_varbin_builder_widths!($builder, |$typed| $body, [u32, u64, i32, i64])
679 };
680}
681
682#[doc(hidden)]
685#[macro_export]
686macro_rules! __match_varbin_builder_widths {
687 ($builder:expr, | $typed:ident | $body:expr, [$($width:ty),+ $(,)?]) => {{
688 let __varbin_builder: &mut dyn $crate::builders::ArrayBuilder = $builder;
689 $crate::__match_varbin_builder_arms!(__varbin_builder, |$typed| $body, [$($width),+])
690 }};
691}
692
693#[doc(hidden)]
695#[macro_export]
696macro_rules! __match_varbin_builder_arms {
697 ($builder:expr, | $typed:ident | $body:expr, []) => {
698 None
699 };
700 ($builder:expr, | $typed:ident | $body:expr, [$head:ty $(, $tail:ty)*]) => {
701 if $builder
702 .as_any()
703 .is::<$crate::builders::VarBinBuilder<$head>>()
704 {
705 let $typed = match $builder
706 .as_any_mut()
707 .downcast_mut::<$crate::builders::VarBinBuilder<$head>>()
708 {
709 Some(typed) => typed,
710 None => unreachable!("builder type checked above"),
711 };
712 Some($body)
713 } else {
714 $crate::__match_varbin_builder_arms!($builder, |$typed| $body, [$($tail),*])
715 }
716 };
717}
718
719#[inline]
722fn prefix_sums<P: AsPrimitive<usize>>(lengths: &[P]) -> impl Iterator<Item = usize> {
723 lengths.iter().scan(0usize, |end, length| {
724 *end = end.wrapping_add(length.as_());
725 Some(*end)
726 })
727}
728
729#[cfg(test)]
730mod tests {
731 use std::mem::MaybeUninit;
732
733 use rstest::rstest;
734 use vortex_error::VortexResult;
735 use vortex_mask::Mask;
736
737 use crate::IntoArray;
738 use crate::VortexSessionExecute;
739 use crate::array_session;
740 use crate::arrays::ChunkedArray;
741 use crate::arrays::ConstantArray;
742 use crate::arrays::VarBinArray;
743 use crate::arrays::VarBinViewArray;
744 use crate::arrays::varbin::VarBinArraySlotsExt;
745 use crate::arrays::varbin::builder::VarBinBuilder;
746 use crate::assert_arrays_eq;
747 use crate::builders::ArrayBuilder;
748 use crate::dtype::DType;
749 use crate::dtype::Nullability::Nullable;
750 use crate::expr::stats::Precision;
751 use crate::expr::stats::Stat;
752 use crate::expr::stats::StatsProviderExt;
753 use crate::scalar::Scalar;
754
755 #[test]
756 fn test_builder() {
757 let mut builder = VarBinBuilder::<i32>::with_capacity_in(
758 DType::Utf8(Nullable),
759 0,
760 vortex_buffer::BufferAllocatorRef::static_ref(),
761 );
762 builder.append(Some(b"hello"));
763 builder.append(None);
764 builder.append(Some(b"world"));
765 let array = builder.finish_into_varbin();
766
767 assert_eq!(array.len(), 3);
768 assert_eq!(array.dtype().nullability(), Nullable);
769 assert_eq!(
770 array
771 .execute_scalar(0, &mut array_session().create_execution_ctx())
772 .unwrap(),
773 Scalar::utf8("hello".to_string(), Nullable)
774 );
775 assert!(
776 array
777 .execute_scalar(1, &mut array_session().create_execution_ctx())
778 .unwrap()
779 .is_null()
780 );
781 }
782
783 #[rstest]
784 #[case(false)]
785 #[case(true)]
786 fn test_append_varbin_to_builder(#[case] large_offsets: bool) -> VortexResult<()> {
787 let source = VarBinArray::from_iter(
788 [
789 Some("prefix"),
790 Some("hello"),
791 None,
792 Some("world"),
793 Some("suffix"),
794 ],
795 DType::Utf8(Nullable),
796 )
797 .into_array()
798 .slice(1..4)?;
799 let mut ctx = array_session().create_execution_ctx();
800
801 let actual = with_offsets(large_offsets, source.dtype().clone(), |builder| {
802 source.append_to_builder(builder, &mut ctx)
803 })?;
804
805 assert_arrays_eq!(actual, source, &mut ctx);
806 Ok(())
807 }
808
809 #[test]
810 fn append_n_values_offset_overflow_returns_error() {
811 let mut builder = VarBinBuilder::<i32>::new_in(
812 DType::Utf8(Nullable),
813 vortex_buffer::BufferAllocatorRef::static_ref(),
814 );
815
816 let result = builder.append_n_values(b"hello", i32::MAX as usize / 5 + 1);
818
819 assert!(result.is_err());
820 assert_eq!(builder.offsets.len(), 1);
821 assert!(builder.data.is_empty());
822 assert_eq!(builder.validity.len(), 0);
823 }
824
825 #[test]
826 fn append_values_rejects_a_short_offset_count() {
827 let mut builder = VarBinBuilder::<i32>::new_in(
828 DType::Utf8(Nullable),
829 vortex_buffer::BufferAllocatorRef::static_ref(),
830 );
831
832 let result = builder.append_values(b"ab", [1usize, 2].into_iter(), &Mask::new_true(3));
833
834 assert!(result.is_err());
835 assert_eq!(builder.offsets.len(), 1);
836 assert!(builder.data.is_empty());
837 }
838
839 #[test]
840 fn append_values_rejects_non_monotonic_offsets() {
841 let mut builder = VarBinBuilder::<i32>::new_in(
842 DType::Utf8(Nullable),
843 vortex_buffer::BufferAllocatorRef::static_ref(),
844 );
845
846 let result = builder.append_values(b"ab", [2usize, 1].into_iter(), &Mask::new_true(2));
847
848 assert!(result.is_err());
849 assert_eq!(builder.offsets.len(), 1);
850 }
851
852 #[test]
853 fn append_decoded_writes_into_the_builder_storage() -> VortexResult<()> {
854 let mut ctx = array_session().create_execution_ctx();
855 let mut builder = VarBinBuilder::<i32>::new_in(
856 DType::Utf8(Nullable),
857 vortex_buffer::BufferAllocatorRef::static_ref(),
858 );
859
860 unsafe {
862 builder.append_decoded(
863 6,
864 4,
865 &[3usize, 0, 3],
866 &Mask::from_iter([true, false, true]),
867 &mut |spare: &mut [MaybeUninit<u8>]| {
868 for (slot, byte) in spare.iter_mut().zip(b"foobar") {
869 slot.write(*byte);
870 }
871 Ok(6)
872 },
873 )?;
874 }
875
876 let expected =
877 VarBinViewArray::from_iter([Some("foo"), None, Some("bar")], DType::Utf8(Nullable));
878 assert_arrays_eq!(builder.finish_into_varbin(), expected, &mut ctx);
879 Ok(())
880 }
881
882 #[test]
885 fn append_decoded_rejects_an_offset_overflow_without_decoding() {
886 let mut builder = VarBinBuilder::<i32>::new_in(
887 DType::Utf8(Nullable),
888 vortex_buffer::BufferAllocatorRef::static_ref(),
889 );
890 let num_bytes = i32::MAX as usize + 1;
891 let mut decoded = false;
892
893 let result = unsafe {
895 builder.append_decoded(num_bytes, 0, &[num_bytes], &Mask::new_true(1), &mut |_| {
896 decoded = true;
897 Ok(0)
898 })
899 };
900
901 assert!(result.is_err());
902 assert!(!decoded, "decode ran before the offset width was checked");
903 assert_eq!(builder.offsets.len(), 1);
904 assert_eq!(builder.validity.len(), 0);
905 }
906
907 #[test]
910 fn append_scalar_repeated_rejects_an_offset_overflow() {
911 let mut builder = VarBinBuilder::<i32>::new_in(
912 DType::Utf8(Nullable),
913 vortex_buffer::BufferAllocatorRef::static_ref(),
914 );
915
916 let result = builder
917 .append_scalar_repeated(&Scalar::utf8("hello", Nullable), i32::MAX as usize / 5 + 1);
918
919 assert!(result.is_err());
920 assert_eq!(builder.offsets.len(), 1);
921 assert!(builder.data.is_empty());
922 assert_eq!(builder.validity.len(), 0);
923 }
924
925 #[test]
926 fn append_decoded_rejects_a_short_decode() {
927 let mut builder = VarBinBuilder::<i32>::new_in(
928 DType::Utf8(Nullable),
929 vortex_buffer::BufferAllocatorRef::static_ref(),
930 );
931
932 let result = unsafe {
935 builder.append_decoded(6, 0, &[3usize, 3], &Mask::new_true(2), &mut |spare| {
936 spare[..3].fill(MaybeUninit::new(b'x'));
937 Ok(3)
938 })
939 };
940
941 assert!(result.is_err());
942 assert_eq!(builder.offsets.len(), 1);
943 assert_eq!(builder.validity.len(), 0);
944 }
945
946 #[rstest]
950 #[case::all_valid(Mask::new_true(2))]
951 #[case::some_valid(Mask::from_iter([true, false, true]))]
952 fn append_valid_slices_rejects_a_byte_count_mismatch(#[case] validity: Mask) {
953 let mut builder = VarBinBuilder::<i32>::new_in(
954 DType::Utf8(Nullable),
955 vortex_buffer::BufferAllocatorRef::static_ref(),
956 );
957 let values = [b"foo".as_slice(), b"quux".as_slice()];
958
959 let mut next = 0;
960 let result = builder.append_valid_slices(6, &validity, |_| {
961 next += 1;
962 values[next - 1]
963 });
964
965 assert!(result.is_err());
966 assert_eq!(builder.offsets.len(), 1);
967 assert!(builder.data.is_empty());
968 assert_eq!(builder.validity.len(), 0);
969 }
970
971 #[test]
972 #[should_panic(expected = "The offset count must be one more than the validity length")]
973 fn finish_rejects_mismatched_validity() {
974 let mut builder = VarBinBuilder::<i32>::new_in(
975 DType::Utf8(Nullable),
976 vortex_buffer::BufferAllocatorRef::static_ref(),
977 );
978 builder.validity.append_true();
979 drop(builder.finish_into_varbin());
980 }
981
982 #[rstest]
983 #[case(false)]
984 #[case(true)]
985 fn test_array_builder_methods(#[case] large_offsets: bool) -> VortexResult<()> {
986 let mut ctx = array_session().create_execution_ctx();
987 let result = with_offsets(large_offsets, DType::Utf8(Nullable), |builder| {
988 builder.reserve_exact(3);
989 builder.append_zero();
990 builder.append_scalar(&Scalar::utf8("hello", Nullable))?;
991 builder.append_null();
992 assert_eq!(builder.len(), 3);
993 Ok(())
994 })?;
995
996 assert_eq!(
997 result.validity()?.execute_mask(3, &mut ctx)?,
998 Mask::from_iter([true, true, false])
999 );
1000 Ok(())
1001 }
1002
1003 #[rstest]
1004 #[case(false)]
1005 #[case(true)]
1006 fn test_append_varbinview_validity_to_builder(#[case] large_offsets: bool) -> VortexResult<()> {
1007 let long = "a value that does not fit inline";
1008 let all_null = VarBinViewArray::from_iter([None::<&str>, None], DType::Utf8(Nullable));
1009 let mixed =
1010 VarBinViewArray::from_iter([Some("hello"), None, Some(long)], DType::Utf8(Nullable));
1011 let expected = VarBinViewArray::from_iter(
1012 [None, None, Some("hello"), None, Some(long)],
1013 DType::Utf8(Nullable),
1014 );
1015 let mut ctx = array_session().create_execution_ctx();
1016
1017 let actual = with_offsets(large_offsets, expected.dtype().clone(), |builder| {
1018 all_null
1019 .clone()
1020 .into_array()
1021 .append_to_builder(builder, &mut ctx)?;
1022 mixed
1023 .clone()
1024 .into_array()
1025 .append_to_builder(builder, &mut ctx)
1026 })?;
1027
1028 assert_arrays_eq!(actual, expected, &mut ctx);
1029 Ok(())
1030 }
1031
1032 #[rstest]
1037 #[case::u32(VarBinBuilder::<u32>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1038 #[case::u64(VarBinBuilder::<u64>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1039 #[case::i32(VarBinBuilder::<i32>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1040 #[case::i64(VarBinBuilder::<i64>::new_in(DType::Utf8(Nullable), vortex_buffer::BufferAllocatorRef::static_ref()))]
1041 fn append_to_every_offset_width(#[case] mut builder: impl ArrayBuilder) -> VortexResult<()> {
1042 let mut ctx = array_session().create_execution_ctx();
1043 let long = "a string that is far too long to be inlined in a view";
1044 let values = [Some("hello"), None, Some(long), Some("")];
1045
1046 let view = VarBinViewArray::from_iter(values, DType::Utf8(Nullable)).into_array();
1047 let varbin = VarBinArray::from_iter(values, DType::Utf8(Nullable)).into_array();
1048 let constant = ConstantArray::new(Scalar::from("hello").into_nullable(), 2).into_array();
1049
1050 for chunk in [&view, &varbin, &constant] {
1051 chunk.append_to_builder(&mut builder, &mut ctx)?;
1052 }
1053
1054 let expected = ChunkedArray::try_new(vec![view, varbin, constant], DType::Utf8(Nullable))?;
1055 assert_arrays_eq!(builder.finish(), expected, &mut ctx);
1056 Ok(())
1057 }
1058
1059 #[test]
1060 fn offsets_have_is_sorted_stat() -> VortexResult<()> {
1061 let mut builder = VarBinBuilder::<i32>::with_capacity_in(
1062 DType::Utf8(Nullable),
1063 0,
1064 vortex_buffer::BufferAllocatorRef::static_ref(),
1065 );
1066 builder.append_value(b"aaa");
1067 builder.push_null();
1068 builder.append_value(b"bbb");
1069 let array = builder.finish_into_varbin();
1070
1071 let is_sorted = array
1072 .offsets()
1073 .statistics()
1074 .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
1075 assert_eq!(is_sorted, Precision::Exact(true));
1076 Ok(())
1077 }
1078
1079 #[test]
1080 fn empty_builder_offsets_have_is_sorted_stat() -> VortexResult<()> {
1081 let mut builder = VarBinBuilder::<i32>::new_in(
1082 DType::Utf8(Nullable),
1083 vortex_buffer::BufferAllocatorRef::static_ref(),
1084 );
1085 let array = builder.finish_into_varbin();
1086
1087 let is_sorted = array
1088 .offsets()
1089 .statistics()
1090 .with_typed_stats_set(|s| s.get_as::<bool>(Stat::IsSorted));
1091 assert_eq!(is_sorted, Precision::Exact(true));
1092 Ok(())
1093 }
1094
1095 fn with_offsets(
1097 large_offsets: bool,
1098 dtype: DType,
1099 f: impl FnOnce(&mut dyn ArrayBuilder) -> VortexResult<()>,
1100 ) -> VortexResult<VarBinArray> {
1101 if large_offsets {
1102 let mut builder = VarBinBuilder::<i64>::with_capacity_in(
1103 dtype,
1104 8,
1105 vortex_buffer::BufferAllocatorRef::static_ref(),
1106 );
1107 f(&mut builder)?;
1108 Ok(builder.finish_into_varbin())
1109 } else {
1110 let mut builder = VarBinBuilder::<i32>::with_capacity_in(
1111 dtype,
1112 8,
1113 vortex_buffer::BufferAllocatorRef::static_ref(),
1114 );
1115 f(&mut builder)?;
1116 Ok(builder.finish_into_varbin())
1117 }
1118 }
1119}