|
4 | 4 | //! Conversion methods and trait implementations of [`From`] and [`Into`] for [`PrimitiveArray`]. |
5 | 5 |
|
6 | 6 | use vortex_buffer::{BitBufferMut, Buffer, BufferMut}; |
7 | | -use vortex_dtype::NativePType; |
8 | | -use vortex_error::vortex_panic; |
| 7 | +use vortex_dtype::{NativePType, Nullability}; |
| 8 | +use vortex_error::{VortexResult, vortex_ensure, vortex_panic}; |
| 9 | +use vortex_vector::{PrimitiveVector, VectorOps, match_each_pvector}; |
9 | 10 |
|
10 | 11 | use crate::arrays::PrimitiveArray; |
11 | 12 | use crate::validity::Validity; |
12 | 13 | use crate::vtable::ValidityHelper; |
13 | 14 | use crate::{ArrayRef, IntoArray}; |
14 | 15 |
|
15 | 16 | impl PrimitiveArray { |
| 17 | + /// Attempts to create a `PrimitiveArray` from a [`PrimitiveVector`] given a [`Nullability`]. |
| 18 | + /// |
| 19 | + /// # Errors |
| 20 | + /// |
| 21 | + /// Returns an error if the nullability is [`NonNullable`](Nullability::NonNullable) and there |
| 22 | + /// are nulls present in the vector. |
| 23 | + pub fn try_from_vector( |
| 24 | + primitive_vector: PrimitiveVector, |
| 25 | + nullability: Nullability, |
| 26 | + ) -> VortexResult<Self> { |
| 27 | + // If we want to create a non-nullable array, then the vector should not have any nulls. |
| 28 | + vortex_ensure!( |
| 29 | + nullability.is_nullable() || primitive_vector.validity().all_true(), |
| 30 | + "tried to create a non-nullable `PrimitiveArray` from a `PrimitiveVector` that had nulls" |
| 31 | + ); |
| 32 | + |
| 33 | + match_each_pvector!(primitive_vector, |v| { |
| 34 | + let (buffer, mask) = v.into_parts(); |
| 35 | + debug_assert_eq!(buffer.len(), mask.len()); |
| 36 | + |
| 37 | + let validity = Validity::from_mask(mask, nullability); |
| 38 | + |
| 39 | + // SAFETY: Since the buffer and the mask came from a valid vector, we know that the |
| 40 | + // length of the buffer and the validity are the same. |
| 41 | + Ok(unsafe { Self::new_unchecked(buffer, validity) }) |
| 42 | + }) |
| 43 | + } |
| 44 | + |
16 | 45 | /// Create a PrimitiveArray from an iterator of `T`. |
17 | 46 | /// NOTE: we cannot impl FromIterator trait since it conflicts with `FromIterator<T>`. |
18 | 47 | pub fn from_option_iter<T: NativePType, I: IntoIterator<Item = Option<T>>>(iter: I) -> Self { |
@@ -107,3 +136,69 @@ impl<T: NativePType> IntoArray for BufferMut<T> { |
107 | 136 | self.freeze().into_array() |
108 | 137 | } |
109 | 138 | } |
| 139 | + |
| 140 | +#[cfg(test)] |
| 141 | +mod tests { |
| 142 | + use vortex_buffer::BufferMut; |
| 143 | + use vortex_dtype::{Nullability, PType}; |
| 144 | + use vortex_mask::MaskMut; |
| 145 | + use vortex_vector::PVector; |
| 146 | + |
| 147 | + use super::*; |
| 148 | + |
| 149 | + #[test] |
| 150 | + fn test_try_from_vector_with_nulls_nullable() { |
| 151 | + // Create a vector with some null values: [Some(1), None, Some(3), Some(4), None]. |
| 152 | + let mut values = BufferMut::<i32>::with_capacity(5); |
| 153 | + values.extend_from_slice(&[1, 0, 3, 4, 0]); |
| 154 | + |
| 155 | + let mut validity = MaskMut::with_capacity(5); |
| 156 | + validity.append_n(true, 1); |
| 157 | + validity.append_n(false, 1); |
| 158 | + validity.append_n(true, 1); |
| 159 | + validity.append_n(true, 1); |
| 160 | + validity.append_n(false, 1); |
| 161 | + |
| 162 | + let pvector = |
| 163 | + PVector::try_new(values.freeze(), validity.freeze()).expect("Failed to create PVector"); |
| 164 | + |
| 165 | + // This should succeed since we're allowing nulls. |
| 166 | + let result = |
| 167 | + PrimitiveArray::try_from_vector(pvector.into(), Nullability::Nullable).unwrap(); |
| 168 | + |
| 169 | + assert_eq!(result.len(), 5); |
| 170 | + assert_eq!(result.ptype(), PType::I32); |
| 171 | + assert!(result.is_valid(0)); |
| 172 | + assert!(!result.is_valid(1)); |
| 173 | + assert!(result.is_valid(2)); |
| 174 | + assert!(result.is_valid(3)); |
| 175 | + assert!(!result.is_valid(4)); |
| 176 | + } |
| 177 | + |
| 178 | + #[test] |
| 179 | + fn test_try_from_vector_non_nullable_with_nulls_errors() { |
| 180 | + // Create a vector with null values: [Some(1), None, Some(3)]. |
| 181 | + let mut values = BufferMut::<i32>::with_capacity(3); |
| 182 | + values.extend_from_slice(&[1, 0, 3]); |
| 183 | + |
| 184 | + let mut validity = MaskMut::with_capacity(3); |
| 185 | + validity.append_n(true, 1); |
| 186 | + validity.append_n(false, 1); |
| 187 | + validity.append_n(true, 1); |
| 188 | + |
| 189 | + let pvector = |
| 190 | + PVector::try_new(values.freeze(), validity.freeze()).expect("Failed to create PVector"); |
| 191 | + |
| 192 | + // This should fail because we're trying to create a non-nullable array from data with |
| 193 | + // nulls. |
| 194 | + let result = PrimitiveArray::try_from_vector(pvector.into(), Nullability::NonNullable); |
| 195 | + |
| 196 | + assert!(result.is_err()); |
| 197 | + assert!( |
| 198 | + result |
| 199 | + .unwrap_err() |
| 200 | + .to_string() |
| 201 | + .contains("non-nullable `PrimitiveArray`") |
| 202 | + ); |
| 203 | + } |
| 204 | +} |
0 commit comments