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