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