Skip to main content

vortex_array/builders/
primitive.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::mem::MaybeUninit;
6
7use vortex_buffer::BufferAllocatorRef;
8use vortex_buffer::BufferMut;
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_ensure;
12use vortex_mask::Mask;
13
14use crate::ArrayRef;
15use crate::ExecutionCtx;
16use crate::IntoArray;
17use crate::arrays::PrimitiveArray;
18use crate::builders::ArrayBuilder;
19use crate::builders::DEFAULT_BUILDER_CAPACITY;
20use crate::builders::LazyBitBufferBuilder;
21use crate::canonical::Canonical;
22use crate::dtype::DType;
23use crate::dtype::NativePType;
24use crate::dtype::Nullability;
25use crate::scalar::Scalar;
26
27/// The builder for building a [`PrimitiveArray`], parametrized by the `PType`.
28pub struct PrimitiveBuilder<T> {
29    dtype: DType,
30    values: BufferMut<T>,
31    nulls: LazyBitBufferBuilder,
32}
33
34impl<T: NativePType> PrimitiveBuilder<T> {
35    /// Creates a new `PrimitiveBuilder` with a capacity of [`DEFAULT_BUILDER_CAPACITY`].
36    #[deprecated(note = "use `new_in` with an explicit allocator")]
37    pub fn new(nullability: Nullability) -> Self {
38        Self::new_in(nullability, BufferAllocatorRef::static_ref())
39    }
40
41    /// Creates a new `PrimitiveBuilder` with the default capacity using `allocator`.
42    pub fn new_in(nullability: Nullability, allocator: &BufferAllocatorRef) -> Self {
43        Self::with_capacity_in(nullability, DEFAULT_BUILDER_CAPACITY, allocator)
44    }
45
46    /// Creates a new `PrimitiveBuilder` with the given `capacity`.
47    #[deprecated(note = "use `with_capacity_in` with an explicit allocator")]
48    pub fn with_capacity(nullability: Nullability, capacity: usize) -> Self {
49        Self::with_capacity_in(nullability, capacity, BufferAllocatorRef::static_ref())
50    }
51
52    /// Creates a new `PrimitiveBuilder` with `capacity` using `allocator`.
53    pub fn with_capacity_in(
54        nullability: Nullability,
55        capacity: usize,
56        allocator: &BufferAllocatorRef,
57    ) -> Self {
58        Self {
59            values: BufferMut::with_capacity_in(capacity, allocator.clone()),
60            nulls: LazyBitBufferBuilder::new(capacity, allocator.clone()),
61            dtype: DType::Primitive(T::PTYPE, nullability),
62        }
63    }
64
65    /// Appends a primitive `value` to the builder.
66    pub fn append_value(&mut self, value: T) {
67        self.values.push(value);
68        self.nulls.append_non_null();
69    }
70
71    /// Appends `n` copies of `value` as non-null entries, directly writing into the buffer.
72    pub fn append_n_values(&mut self, value: T, n: usize) {
73        self.values.push_n(value, n);
74        self.nulls.append_n_non_nulls(n);
75    }
76
77    /// Returns the raw primitive values in this builder as a slice.
78    pub fn values(&self) -> &[T] {
79        self.values.as_ref()
80    }
81
82    /// Returns the raw primitive values in this builder as a mutable slice.
83    pub fn values_mut(&mut self) -> &mut [T] {
84        self.values.as_mut()
85    }
86
87    /// Create a new handle to the next `len` uninitialized values in the builder.
88    ///
89    /// All reads/writes through the handle to the values buffer or the validity buffer will operate
90    /// on indices relative to the start of the range.
91    ///
92    /// # Panics
93    ///
94    /// Panics if `len` is 0 or if the current length of the builder plus `len` would exceed the
95    /// capacity of the builder's memory.
96    ///
97    /// ## Example
98    ///
99    /// ```
100    /// use std::mem::MaybeUninit;
101    /// use vortex_array::builders::{ArrayBuilder, PrimitiveBuilder};
102    /// use vortex_array::dtype::Nullability;
103    /// use vortex_array::memory::BufferAllocatorRef;
104    ///
105    /// // Create a new builder.
106    /// let mut builder: PrimitiveBuilder<i32> =
107    ///     PrimitiveBuilder::with_capacity_in(Nullability::NonNullable, 5, BufferAllocatorRef::static_ref());
108    ///
109    /// // Populate the values.
110    /// let mut uninit_range = builder.uninit_range(5);
111    /// uninit_range.copy_from_slice(0, &[0, 1, 2, 3, 4]);
112    ///
113    /// // SAFETY: We have initialized all 5 values in the range, and since the array builder is
114    /// // non-nullable, we don't need to set any null bits.
115    /// unsafe { uninit_range.finish(); }
116    ///
117    /// let built = builder.finish_into_primitive();
118    ///
119    /// assert_eq!(built.as_slice::<i32>(), &[0i32, 1, 2, 3, 4]);
120    /// ```
121    pub fn uninit_range(&mut self, len: usize) -> UninitRange<'_, T> {
122        assert_ne!(0, len, "cannot create an uninit range of length 0");
123
124        let current_len = self.values.len();
125        assert!(
126            current_len + len <= self.values.capacity(),
127            "uninit_range of len {len} exceeds builder with length {} and capacity {}",
128            current_len,
129            self.values.capacity()
130        );
131
132        UninitRange { len, builder: self }
133    }
134
135    /// Finishes the builder directly into a [`PrimitiveArray`].
136    pub fn finish_into_primitive(&mut self) -> PrimitiveArray {
137        let validity = self
138            .nulls
139            .finish_with_nullability(self.dtype().nullability());
140
141        let values = self.values.take().freeze();
142        PrimitiveArray::new(values, validity)
143    }
144
145    /// Extends the primitive array with an iterator.
146    pub fn extend_with_iterator(&mut self, iter: impl IntoIterator<Item = T>, mask: &Mask) {
147        self.values.extend(iter);
148        self.nulls.append_validity_mask(mask);
149    }
150
151    pub(crate) fn append_primitive_array(
152        &mut self,
153        array: &PrimitiveArray,
154        ctx: &mut ExecutionCtx,
155    ) -> VortexResult<()> {
156        debug_assert_eq!(
157            array.ptype(),
158            T::PTYPE,
159            "Cannot append primitive array with different ptype"
160        );
161
162        self.values.extend_from_slice(array.as_slice::<T>());
163        self.nulls.append_validity_mask(
164            &array
165                .as_ref()
166                .validity()
167                .vortex_expect("validity_mask")
168                .execute_mask(array.as_ref().len(), ctx)?,
169        );
170        Ok(())
171    }
172}
173
174impl<T: NativePType> ArrayBuilder for PrimitiveBuilder<T> {
175    fn as_any(&self) -> &dyn Any {
176        self
177    }
178
179    fn as_any_mut(&mut self) -> &mut dyn Any {
180        self
181    }
182
183    fn dtype(&self) -> &DType {
184        &self.dtype
185    }
186
187    fn len(&self) -> usize {
188        self.values.len()
189    }
190
191    fn append_zeros(&mut self, n: usize) {
192        self.values.push_n(T::default(), n);
193        self.nulls.append_n_non_nulls(n);
194    }
195
196    unsafe fn append_nulls_unchecked(&mut self, n: usize) {
197        self.values.push_n(T::default(), n);
198        self.nulls.append_n_nulls(n);
199    }
200
201    fn append_scalar(&mut self, scalar: &Scalar) -> VortexResult<()> {
202        vortex_ensure!(
203            scalar.dtype() == self.dtype(),
204            "PrimitiveBuilder expected scalar with dtype {}, got {}",
205            self.dtype(),
206            scalar.dtype()
207        );
208
209        if let Some(pv) = scalar.as_primitive().pvalue() {
210            self.append_value(pv.cast::<T>()?)
211        } else {
212            self.append_null()
213        }
214
215        Ok(())
216    }
217
218    fn reserve_exact(&mut self, additional: usize) {
219        self.values.reserve(additional);
220        self.nulls.reserve_exact(additional);
221    }
222
223    fn finish(&mut self) -> ArrayRef {
224        self.finish_into_primitive().into_array()
225    }
226
227    fn finish_into_canonical(&mut self, _ctx: &mut ExecutionCtx) -> Canonical {
228        Canonical::Primitive(self.finish_into_primitive())
229    }
230}
231
232/// A range of uninitialized values in the primitive builder that can be filled.
233pub struct UninitRange<'a, T> {
234    /// The length of the uninitialized range.
235    ///
236    /// This is guaranteed to be within the memory capacity of the builder.
237    len: usize,
238
239    /// A mutable reference to the builder.
240    ///
241    /// Since this is a mutable reference, we can guarantee that nothing else can modify the builder
242    /// while this `UninitRange` exists.
243    builder: &'a mut PrimitiveBuilder<T>,
244}
245
246impl<T> UninitRange<'_, T> {
247    /// Returns the length of this uninitialized range.
248    #[inline]
249    pub fn len(&self) -> usize {
250        self.len
251    }
252
253    /// Returns true if this range has zero length.
254    #[inline]
255    pub fn is_empty(&self) -> bool {
256        self.len == 0
257    }
258
259    /// Set a value at the given index within this range.
260    ///
261    /// # Panics
262    ///
263    /// Panics if the index is out of bounds.
264    #[inline]
265    pub fn set_value(&mut self, index: usize, value: T) {
266        assert!(index < self.len, "index out of bounds");
267        let spare = self.builder.values.spare_capacity_mut();
268        spare[index] = MaybeUninit::new(value);
269    }
270
271    /// Append a [`Mask`] to this builder's null buffer.
272    ///
273    /// # Panics
274    ///
275    /// Panics if the mask length is not equal to the the length of the current `UninitRange`.
276    ///
277    /// # Safety
278    ///
279    /// - The caller must ensure that they safely initialize `mask.len()` primitive values via
280    ///   [`UninitRange::copy_from_slice`].
281    /// - The caller must also ensure that they only call this method once.
282    pub unsafe fn append_mask(&mut self, mask: &Mask) {
283        assert_eq!(
284            mask.len(),
285            self.len,
286            "Tried to append a mask to an `UninitRange` that was beyond the allowed range"
287        );
288
289        // TODO(connor): Ideally, we would call this function `set_mask` and directly set all of the
290        // bits (so that we can call this multiple times), but the underlying `BooleanBuffer` does
291        // not have an easy way to do this correctly.
292
293        self.builder.nulls.append_validity_mask(mask);
294    }
295
296    /// Set a validity bit at the given index.
297    ///
298    /// The index is relative to the start of this range (not relative to the values already in the
299    /// builder).
300    ///
301    /// Note that this will have no effect if the builder is non-nullable.
302    pub fn set_validity_bit(&mut self, index: usize, v: bool) {
303        assert!(index < self.len, "set_bit index out of bounds");
304        // Note that this won't panic because we can only create an `UninitRange` within the
305        // capacity of the builder (it will not automatically resize).
306        let absolute_index = self.builder.values.len() + index;
307        self.builder.nulls.set_bit(absolute_index, v);
308    }
309
310    /// Set values from an initialized range.
311    ///
312    /// Note that the input `offset` should be an offset relative to the local `UninitRange`, not
313    /// the entire `PrimitiveBuilder`.
314    pub fn copy_from_slice(&mut self, local_offset: usize, src: &[T])
315    where
316        T: Copy,
317    {
318        debug_assert!(
319            local_offset + src.len() <= self.len,
320            "tried to copy a slice into a `UninitRange` past its boundary"
321        );
322
323        // Note: spare_capacity_mut() returns the spare capacity starting from the current length,
324        // so we just use local_offset directly.
325        let dst =
326            &mut self.builder.values.spare_capacity_mut()[local_offset..local_offset + src.len()];
327        dst.write_copy_of_slice(src);
328    }
329
330    /// Get a mutable slice of uninitialized memory at the specified offset within this range.
331    ///
332    /// Note that the offsets are relative to this local range, not to the values already in the
333    /// builder.
334    ///
335    /// # Safety
336    ///
337    /// The caller must ensure that they properly initialize the returned memory before calling
338    /// `finish()` on this range.
339    ///
340    /// # Panics
341    ///
342    /// Panics if `offset + len` exceeds the range bounds.
343    pub unsafe fn slice_uninit_mut(&mut self, offset: usize, len: usize) -> &mut [MaybeUninit<T>] {
344        assert!(
345            offset + len <= self.len,
346            "slice_uninit_mut: offset {} + len {} exceeds range length {}",
347            offset,
348            len,
349            self.len
350        );
351        &mut self.builder.values.spare_capacity_mut()[offset..offset + len]
352    }
353
354    /// Finish building this range, marking it as initialized and advancing the length of the
355    /// underlying values buffer.
356    ///
357    /// # Safety
358    ///
359    /// The caller must ensure that they have safely initialized all `len` values via
360    /// [`copy_from_slice()`] or [`set_value()`], as well as correctly set all of the null bits via
361    /// [`set_validity_bit()`] or [`append_mask()`] if the builder is nullable.
362    ///
363    /// [`copy_from_slice()`]: UninitRange::copy_from_slice
364    /// [`set_value()`]: UninitRange::set_value
365    /// [`set_validity_bit()`]: UninitRange::set_validity_bit
366    /// [`append_mask()`]: UninitRange::append_mask
367    pub unsafe fn finish(self) {
368        // SAFETY: constructor enforces that current length + len does not exceed the capacity of the array.
369        let new_len = self.builder.values.len() + self.len;
370        unsafe { self.builder.values.set_len(new_len) };
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use vortex_error::VortexExpect;
377
378    use super::*;
379    use crate::VortexSessionExecute;
380    use crate::array_session;
381    use crate::assert_arrays_eq;
382
383    /// REGRESSION TEST: This test verifies that multiple sequential ranges have correct offsets.
384    ///
385    /// This would have caught the `Deref` bug where it always returned from the start of the
386    /// buffer.
387    #[test]
388    fn test_multiple_uninit_ranges_correct_offsets() {
389        let mut ctx = array_session().create_execution_ctx();
390        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
391            Nullability::NonNullable,
392            10,
393            BufferAllocatorRef::static_ref(),
394        );
395
396        // First range.
397        let mut range1 = builder.uninit_range(3);
398        range1.copy_from_slice(0, &[1, 2, 3]);
399
400        // SAFETY: We initialized all 3 values.
401        unsafe {
402            range1.finish();
403        }
404
405        // Verify the builder now has these values.
406        assert_eq!(builder.values(), &[1, 2, 3]);
407
408        // Second range - this would fail with the old Deref implementation.
409        let mut range2 = builder.uninit_range(2);
410
411        // Set values using copy_from_slice.
412        range2.copy_from_slice(0, &[4, 5]);
413
414        // SAFETY: We initialized both values.
415        unsafe {
416            range2.finish();
417        }
418
419        // Verify the builder now has all 5 values.
420        assert_eq!(builder.values(), &[1, 2, 3, 4, 5]);
421
422        let array = builder.finish_into_primitive();
423        assert_arrays_eq!(
424            array,
425            PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]),
426            &mut ctx
427        );
428    }
429
430    /// REGRESSION TEST: This test verifies that `append_mask` was correctly moved from
431    /// `PrimitiveBuilder` to `UninitRange`.
432    ///
433    /// The old API had `append_mask` on the builder, which was confusing when used with ranges.
434    /// This test ensures the new API works correctly.
435    #[test]
436    fn test_append_mask_on_uninit_range() {
437        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
438            Nullability::Nullable,
439            5,
440            BufferAllocatorRef::static_ref(),
441        );
442        let mut range = builder.uninit_range(3);
443
444        // Create a mask for 3 values.
445        let mask = Mask::from_iter([true, false, true]);
446
447        // SAFETY: We're about to initialize the values.
448        unsafe {
449            range.append_mask(&mask);
450        }
451
452        // Initialize the values.
453        range.copy_from_slice(0, &[10, 20, 30]);
454
455        // SAFETY: We've initialized all values and set the mask.
456        unsafe {
457            range.finish();
458        }
459
460        let array = builder.finish_into_primitive();
461        assert_eq!(array.len(), 3);
462        // Check validity using scalar_at - nulls will return is_null() = true.
463        assert!(
464            !array
465                .execute_scalar(0, &mut array_session().create_execution_ctx())
466                .unwrap()
467                .is_null()
468        );
469        assert!(
470            array
471                .execute_scalar(1, &mut array_session().create_execution_ctx())
472                .unwrap()
473                .is_null()
474        );
475        assert!(
476            !array
477                .execute_scalar(2, &mut array_session().create_execution_ctx())
478                .unwrap()
479                .is_null()
480        );
481    }
482
483    /// REGRESSION TEST: This test verifies that `append_mask` validates the mask length.
484    ///
485    /// This ensures that masks can only be appended if they match the range length.
486    #[test]
487    #[should_panic(
488        expected = "Tried to append a mask to an `UninitRange` that was beyond the allowed range"
489    )]
490    fn test_append_mask_wrong_length_panics() {
491        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
492            Nullability::Nullable,
493            10,
494            BufferAllocatorRef::static_ref(),
495        );
496        let mut range = builder.uninit_range(5);
497
498        // Try to append a mask with wrong length (3 instead of 5).
499        let wrong_mask = Mask::from_iter([true, false, true]);
500
501        // SAFETY: This is expected to panic due to length mismatch.
502        unsafe {
503            range.append_mask(&wrong_mask);
504        }
505    }
506
507    /// Test that `copy_from_slice` works correctly with different offsets.
508    ///
509    /// This verifies the new simplified API without the redundant `len` parameter.
510    #[test]
511    fn test_copy_from_slice_with_offsets() {
512        let mut ctx = array_session().create_execution_ctx();
513        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
514            Nullability::NonNullable,
515            10,
516            BufferAllocatorRef::static_ref(),
517        );
518        let mut range = builder.uninit_range(6);
519
520        // Copy to different offsets.
521        range.copy_from_slice(0, &[1, 2]);
522        range.copy_from_slice(2, &[3, 4]);
523        range.copy_from_slice(4, &[5, 6]);
524
525        // SAFETY: We've initialized all 6 values.
526        unsafe {
527            range.finish();
528        }
529
530        let array = builder.finish_into_primitive();
531        assert_arrays_eq!(
532            array,
533            PrimitiveArray::from_iter([1i32, 2, 3, 4, 5, 6]),
534            &mut ctx
535        );
536    }
537
538    /// Test that `set_bit` uses relative indexing within the range.
539    ///
540    /// Note: `set_bit` requires the null buffer to already be initialized, so we first
541    /// use `append_mask` to set up the buffer, then demonstrate that `set_bit` can
542    /// modify individual bits with relative indexing.
543    #[test]
544    fn test_set_bit_relative_indexing() {
545        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
546            Nullability::Nullable,
547            10,
548            BufferAllocatorRef::static_ref(),
549        );
550
551        // First add some values to the builder.
552        builder.append_value(100);
553        builder.append_value(200);
554
555        // Create a range for new values.
556        let mut range = builder.uninit_range(3);
557
558        // Use append_mask to initialize the validity buffer for this range.
559        let initial_mask = Mask::from_iter([false, false, false]);
560        // SAFETY: We're about to initialize the values.
561        unsafe {
562            range.append_mask(&initial_mask);
563        }
564
565        // Now we can use set_bit to modify individual bits with relative indexing.
566        range.set_validity_bit(0, true); // Change first bit to valid
567        range.set_validity_bit(2, true); // Change third bit to valid
568        // Leave middle bit as false (null)
569
570        // Initialize the values.
571        range.copy_from_slice(0, &[10, 20, 30]);
572
573        // SAFETY: We've initialized all 3 values and set their validity.
574        unsafe {
575            range.finish();
576        }
577
578        let array = builder.finish_into_primitive();
579
580        // Verify the total length and values.
581        assert_eq!(array.len(), 5);
582        assert_eq!(array.as_slice::<i32>(), &[100, 200, 10, 20, 30]);
583
584        // Check validity - the first two should be valid (from append_value).
585        assert!(
586            !array
587                .execute_scalar(0, &mut array_session().create_execution_ctx())
588                .unwrap()
589                .is_null()
590        ); // initial value 100
591        assert!(
592            !array
593                .execute_scalar(1, &mut array_session().create_execution_ctx())
594                .unwrap()
595                .is_null()
596        ); // initial value 200
597
598        // Check the range items with modified validity.
599        assert!(
600            !array
601                .execute_scalar(2, &mut array_session().create_execution_ctx())
602                .unwrap()
603                .is_null()
604        ); // range index 0 - set to valid
605        assert!(
606            array
607                .execute_scalar(3, &mut array_session().create_execution_ctx())
608                .unwrap()
609                .is_null()
610        ); // range index 1 - left as null
611        assert!(
612            !array
613                .execute_scalar(4, &mut array_session().create_execution_ctx())
614                .unwrap()
615                .is_null()
616        ); // range index 2 - set to valid
617    }
618
619    /// Test that creating a zero-length uninit range panics.
620    #[test]
621    #[should_panic(expected = "cannot create an uninit range of length 0")]
622    fn test_zero_length_uninit_range_panics() {
623        let mut builder = PrimitiveBuilder::<i32>::new_in(
624            Nullability::NonNullable,
625            BufferAllocatorRef::static_ref(),
626        );
627        let _range = builder.uninit_range(0);
628    }
629
630    /// Test that creating an uninit range exceeding capacity panics.
631    #[test]
632    #[should_panic(expected = "uninit_range of len 261 exceeds builder with length 0 and capacity")]
633    fn test_uninit_range_exceeds_capacity_panics() {
634        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
635            Nullability::NonNullable,
636            5,
637            BufferAllocatorRef::static_ref(),
638        );
639        let _range = builder.uninit_range(261);
640    }
641
642    /// Test that `copy_from_slice` debug asserts on out-of-bounds access.
643    ///
644    /// Note: This only panics in debug mode due to `debug_assert!`.
645    #[test]
646    #[cfg(debug_assertions)]
647    #[should_panic(expected = "tried to copy a slice into a `UninitRange` past its boundary")]
648    fn test_copy_from_slice_out_of_bounds() {
649        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
650            Nullability::NonNullable,
651            10,
652            BufferAllocatorRef::static_ref(),
653        );
654        let mut range = builder.uninit_range(3);
655
656        // Try to copy 3 elements starting at offset 1 (would need 4 slots total).
657        range.copy_from_slice(1, &[1, 2, 3]);
658    }
659
660    /// Test that the unsafe contract of `finish` is documented and works correctly.
661    ///
662    /// This test demonstrates proper usage of the unsafe `finish` method.
663    #[test]
664    fn test_finish_unsafe_contract() {
665        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
666            Nullability::Nullable,
667            5,
668            BufferAllocatorRef::static_ref(),
669        );
670        let mut range = builder.uninit_range(3);
671
672        // Set validity mask.
673        let mask = Mask::from_iter([true, true, false]);
674        // SAFETY: We're about to initialize the matching number of values.
675        unsafe {
676            range.append_mask(&mask);
677        }
678
679        // Initialize all values.
680        range.copy_from_slice(0, &[10, 20, 30]);
681
682        // SAFETY: We have initialized all 3 values and set their validity.
683        unsafe {
684            range.finish();
685        }
686
687        let array = builder.finish_into_primitive();
688        assert_eq!(array.len(), 3);
689        assert_eq!(array.as_slice::<i32>(), &[10, 20, 30]);
690    }
691
692    #[test]
693    fn test_append_scalar() {
694        use crate::dtype::DType;
695        use crate::scalar::Scalar;
696
697        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
698            Nullability::Nullable,
699            10,
700            BufferAllocatorRef::static_ref(),
701        );
702
703        // Test appending a valid primitive value.
704        let scalar1 = Scalar::primitive(42i32, Nullability::Nullable);
705        builder.append_scalar(&scalar1).unwrap();
706
707        // Test appending another value.
708        let scalar2 = Scalar::primitive(84i32, Nullability::Nullable);
709        builder.append_scalar(&scalar2).unwrap();
710
711        // Test appending null value.
712        let null_scalar = Scalar::null(DType::Primitive(
713            crate::dtype::PType::I32,
714            Nullability::Nullable,
715        ));
716        builder.append_scalar(&null_scalar).unwrap();
717
718        let array = builder.finish_into_primitive();
719        assert_eq!(array.len(), 3);
720
721        // Check actual values.
722        let values = array.as_slice::<i32>();
723        assert_eq!(values[0], 42);
724        assert_eq!(values[1], 84);
725        // values[2] might be any value since it's null.
726
727        // Check validity - first two should be valid, third should be null.
728        let mut ctx = array_session().create_execution_ctx();
729        assert!(
730            array
731                .validity()
732                .vortex_expect("primitive validity should be derivable")
733                .execute_is_valid(0, &mut ctx)
734                .unwrap()
735        );
736        assert!(
737            array
738                .validity()
739                .vortex_expect("primitive validity should be derivable")
740                .execute_is_valid(1, &mut ctx)
741                .unwrap()
742        );
743        assert!(
744            !array
745                .validity()
746                .vortex_expect("primitive validity should be derivable")
747                .execute_is_valid(2, &mut ctx)
748                .unwrap()
749        );
750
751        // Test wrong dtype error.
752        let mut builder = PrimitiveBuilder::<i32>::with_capacity_in(
753            Nullability::NonNullable,
754            10,
755            BufferAllocatorRef::static_ref(),
756        );
757        let wrong_scalar = Scalar::from(true);
758        assert!(builder.append_scalar(&wrong_scalar).is_err());
759    }
760}