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