1mod vtable;
5
6pub(crate) mod compute;
7
8use vortex_error::VortexResult;
9use vortex_error::vortex_ensure;
10
11pub use self::vtable::Variant;
12pub use self::vtable::VariantArray;
13
14pub(crate) fn initialize(session: &vortex_session::VortexSession) {
15 vtable::initialize(session);
16}
17
18use crate::ArrayRef;
19use crate::array::Array;
20use crate::array::ArrayParts;
21use crate::array::EmptyArrayData;
22use crate::array_slots;
23use crate::dtype::DType;
24
25#[array_slots(Variant)]
35pub struct VariantSlots {
36 #[slot(0)]
38 pub core_storage: ArrayRef,
39 #[slot(1)]
43 pub shredded: Option<ArrayRef>,
44}
45
46impl Array<Variant> {
47 pub fn try_new(core_storage: ArrayRef, shredded: Option<ArrayRef>) -> VortexResult<Self> {
55 let dtype = core_storage.dtype().clone();
56 vortex_ensure!(
57 matches!(dtype, DType::Variant(_)),
58 "VariantArray core_storage dtype must be Variant, found {dtype}"
59 );
60 let len = core_storage.len();
61 let stats = core_storage.statistics().to_owned();
62 Ok(Array::try_from_parts(ArrayParts::new(
63 Variant,
64 dtype,
65 len,
66 EmptyArrayData,
67 VariantSlots {
68 core_storage,
69 shredded,
70 }
71 .into_slots(),
72 ))?
73 .with_stats_set(stats))
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use vortex_buffer::buffer;
80 use vortex_error::VortexResult;
81 use vortex_error::vortex_err;
82 use vortex_mask::Mask;
83
84 use crate::ArrayRef;
85 use crate::Canonical;
86 use crate::IntoArray;
87 use crate::VortexSessionExecute;
88 use crate::array_session;
89 use crate::arrays::BoolArray;
90 use crate::arrays::ChunkedArray;
91 use crate::arrays::ConstantArray;
92 use crate::arrays::PrimitiveArray;
93 use crate::arrays::StructArray;
94 use crate::arrays::VariantArray;
95 use crate::arrays::variant::VariantArraySlotsExt;
96 use crate::assert_arrays_eq;
97 use crate::builtins::ArrayBuiltins;
98 use crate::dtype::DType;
99 use crate::dtype::Nullability;
100 use crate::dtype::PType;
101 use crate::expr::root;
102 use crate::expr::variant_get;
103 use crate::scalar::Scalar;
104 use crate::scalar_fn::fns::variant_get::VariantPath;
105
106 fn core_storage(len: usize) -> ArrayRef {
107 ConstantArray::new(
108 Scalar::variant(Scalar::primitive(1i32, Nullability::NonNullable)),
109 len,
110 )
111 .into_array()
112 }
113
114 fn row_storage(values: impl IntoIterator<Item = i32>) -> VortexResult<ArrayRef> {
115 Ok(ChunkedArray::try_new(
116 values.into_iter().map(|value| {
117 ConstantArray::new(
118 Scalar::variant(Scalar::primitive(value, Nullability::NonNullable)),
119 1,
120 )
121 .into_array()
122 }),
123 DType::Variant(Nullability::NonNullable),
124 )?
125 .into_array())
126 }
127
128 fn variant_with_shredded(
129 core_values: impl IntoIterator<Item = i32>,
130 shredded_values: impl IntoIterator<Item = i32>,
131 ) -> VortexResult<VariantArray> {
132 VariantArray::try_new(
133 row_storage(core_values)?,
134 Some(PrimitiveArray::from_iter(shredded_values).into_array()),
135 )
136 }
137
138 fn execute_variant(array: ArrayRef) -> VortexResult<VariantArray> {
139 let mut ctx = array_session().create_execution_ctx();
140 let Canonical::Variant(variant) = array.execute::<Canonical>(&mut ctx)? else {
141 return Err(vortex_err!("expected canonical variant array"));
142 };
143 Ok(variant)
144 }
145
146 fn assert_variant_rows(
147 array: &VariantArray,
148 expected_core: &[Option<i32>],
149 expected_shredded: &[Option<i32>],
150 ) -> VortexResult<()> {
151 assert_variant_core_rows(array, expected_core)?;
152 assert_eq!(array.len(), expected_shredded.len());
153
154 let shredded = array
155 .shredded()
156 .ok_or_else(|| vortex_err!("expected shredded child"))?;
157 let mut ctx = array_session().create_execution_ctx();
158 let shredded = shredded.clone().execute::<PrimitiveArray>(&mut ctx)?;
159 let expected_shredded_array = if let Some(values) = expected_shredded
160 .iter()
161 .copied()
162 .collect::<Option<Vec<_>>>()
163 {
164 PrimitiveArray::from_iter(values)
165 } else {
166 PrimitiveArray::from_option_iter(expected_shredded.iter().copied())
167 };
168 assert_arrays_eq!(shredded, expected_shredded_array, &mut ctx);
169
170 Ok(())
171 }
172
173 fn assert_variant_core_rows(
174 array: &VariantArray,
175 expected_core: &[Option<i32>],
176 ) -> VortexResult<()> {
177 assert_eq!(array.len(), expected_core.len());
178
179 let mut ctx = array_session().create_execution_ctx();
180 for (idx, expected) in expected_core.iter().enumerate() {
181 let scalar = array.core_storage().execute_scalar(idx, &mut ctx)?;
182 let variant = scalar.as_variant();
183 match expected {
184 Some(expected) => {
185 let value = variant
186 .value()
187 .ok_or_else(|| vortex_err!("expected non-null variant row"))?;
188 assert_eq!(value.as_primitive().typed_value::<i32>(), Some(*expected));
189 }
190 None => assert!(variant.is_null()),
191 }
192 }
193
194 Ok(())
195 }
196
197 #[test]
198 fn try_new_exposes_core_storage_without_shredded() -> VortexResult<()> {
199 let core_storage = core_storage(2);
200
201 let variant = VariantArray::try_new(core_storage.clone(), None)?;
202
203 assert_eq!(variant.dtype(), core_storage.dtype());
204 assert_eq!(variant.len(), 2);
205 assert_eq!(variant.core_storage().dtype(), core_storage.dtype());
206 assert!(variant.shredded().is_none());
207
208 Ok(())
209 }
210
211 #[test]
212 fn try_new_exposes_core_storage_and_shredded() -> VortexResult<()> {
213 let core_storage = core_storage(3);
214 let shredded = buffer![10i32, 20, 30].into_array();
215
216 let variant = VariantArray::try_new(core_storage.clone(), Some(shredded.clone()))?;
217
218 assert_eq!(variant.dtype(), &DType::Variant(Nullability::NonNullable));
219 assert_eq!(variant.len(), 3);
220 assert_eq!(variant.core_storage().dtype(), core_storage.dtype());
221 assert_eq!(variant.core_storage().len(), core_storage.len());
222 assert_eq!(
223 variant.shredded().map(|child| child.dtype()),
224 Some(shredded.dtype())
225 );
226 assert_eq!(
227 variant.shredded().map(|child| child.len()),
228 Some(shredded.len())
229 );
230 assert_eq!(variant.as_ref().slot_name(0), "core_storage");
231 assert_eq!(variant.as_ref().slot_name(1), "shredded");
232
233 Ok(())
234 }
235
236 #[test]
237 fn try_new_rejects_non_variant_core_storage() {
238 let core_storage = PrimitiveArray::from_iter([1i32, 2, 3]).into_array();
239
240 assert!(VariantArray::try_new(core_storage, None).is_err());
241 }
242
243 #[test]
244 fn try_new_rejects_shredded_length_mismatch() {
245 let core_storage = core_storage(3);
246 let shredded = buffer![10i32, 20].into_array();
247
248 assert!(VariantArray::try_new(core_storage, Some(shredded)).is_err());
249 }
250
251 #[test]
252 fn scalar_at_merges_shredded_with_core_storage() -> VortexResult<()> {
253 let dtype = DType::Variant(Nullability::Nullable);
254 let core_chunks = [Some(1i32), None, Some(3)]
255 .into_iter()
256 .map(|value| {
257 let scalar = match value {
258 Some(value) => {
259 Scalar::variant(Scalar::primitive(value, Nullability::NonNullable))
260 .cast(&dtype)?
261 }
262 None => Scalar::null(dtype.clone()),
263 };
264 Ok(ConstantArray::new(scalar, 1).into_array())
265 })
266 .collect::<VortexResult<Vec<_>>>()?;
267 let core_storage = ChunkedArray::try_new(core_chunks, dtype)?.into_array();
268 let shredded = PrimitiveArray::from_option_iter([Some(10i32), Some(20), None]).into_array();
269 let variant = VariantArray::try_new(core_storage, Some(shredded))?;
270
271 let mut ctx = array_session().create_execution_ctx();
272 for (idx, expected) in [Some(10i32), None, Some(3)].into_iter().enumerate() {
273 let scalar = variant.execute_scalar(idx, &mut ctx)?;
274 let variant = scalar.as_variant();
275 match expected {
276 Some(expected) => {
277 let value = variant
278 .value()
279 .ok_or_else(|| vortex_err!("expected non-null variant row"))?;
280 assert_eq!(value.as_primitive().typed_value::<i32>(), Some(expected));
281 }
282 None => assert!(variant.is_null()),
283 }
284 }
285
286 Ok(())
287 }
288
289 #[test]
290 fn slice_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
291 let variant = variant_with_shredded(0..5, 10..15)?;
292
293 let sliced = execute_variant(variant.into_array().slice(1..4)?)?;
294
295 assert_variant_rows(
296 &sliced,
297 &[Some(1), Some(2), Some(3)],
298 &[Some(11), Some(12), Some(13)],
299 )
300 }
301
302 #[test]
303 fn filter_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
304 let variant = variant_with_shredded(0..5, 10..15)?;
305
306 let filtered = execute_variant(
307 variant
308 .into_array()
309 .filter(Mask::from_iter([true, false, true, false, true]))?,
310 )?;
311
312 assert_variant_rows(
313 &filtered,
314 &[Some(0), Some(2), Some(4)],
315 &[Some(10), Some(12), Some(14)],
316 )
317 }
318
319 #[test]
320 fn take_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
321 let variant = variant_with_shredded(0..5, 10..15)?;
322
323 let taken = execute_variant(
324 variant
325 .into_array()
326 .take(buffer![4u64, 1, 3].into_array())?,
327 )?;
328
329 assert_variant_rows(
330 &taken,
331 &[Some(4), Some(1), Some(3)],
332 &[Some(14), Some(11), Some(13)],
333 )
334 }
335
336 #[test]
337 fn mask_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
338 let variant = variant_with_shredded(0..5, 10..15)?;
339 let mask = BoolArray::from_iter([true, false, true, false, true]).into_array();
340
341 let masked = execute_variant(variant.into_array().mask(mask)?)?;
342
343 assert_variant_rows(
344 &masked,
345 &[Some(0), None, Some(2), None, Some(4)],
346 &[Some(10), None, Some(12), None, Some(14)],
347 )
348 }
349
350 #[test]
351 fn mask_preserves_chunked_core_storage_validity() -> VortexResult<()> {
352 let dtype = DType::Variant(Nullability::Nullable);
353 let core_chunks = [Some(1i32), None, Some(3), Some(4)]
354 .into_iter()
355 .map(|value| {
356 let scalar = match value {
357 Some(value) => {
358 Scalar::variant(Scalar::primitive(value, Nullability::NonNullable))
359 .cast(&dtype)?
360 }
361 None => Scalar::null(dtype.clone()),
362 };
363 Ok(ConstantArray::new(scalar, 1).into_array())
364 })
365 .collect::<VortexResult<Vec<_>>>()?;
366 let core_storage = ChunkedArray::try_new(core_chunks, dtype)?.into_array();
367 let variant = VariantArray::try_new(core_storage, None)?;
368 let mask = BoolArray::from_iter([true, true, false, true]).into_array();
369
370 let masked = execute_variant(variant.into_array().mask(mask)?)?;
371
372 assert_variant_core_rows(&masked, &[Some(1), None, None, Some(4)])
373 }
374
375 #[test]
376 fn variant_get_keeps_valid_shredded_rows_for_matching_dtype() -> VortexResult<()> {
377 let mut ctx = array_session().create_execution_ctx();
378 let core_storage = row_storage([1, 2, 3])?;
379 let shredded = StructArray::try_from_iter([(
380 "a",
381 PrimitiveArray::from_iter([10i32, 20, 30]).into_array(),
382 )])?;
383 let variant = VariantArray::try_new(core_storage, Some(shredded.into_array()))?;
384 let expr = variant_get(
385 root(),
386 VariantPath::field("a"),
387 Some(DType::Primitive(PType::I32, Nullability::NonNullable)),
388 );
389
390 let result = variant
391 .into_array()
392 .apply(&expr)?
393 .execute::<PrimitiveArray>(&mut array_session().create_execution_ctx())?;
394
395 assert_arrays_eq!(
396 result,
397 PrimitiveArray::from_option_iter([Some(10i32), Some(20), Some(30)]),
398 &mut ctx
399 );
400 Ok(())
401 }
402
403 #[test]
404 fn variant_get_treats_value_and_typed_value_as_logical_field_names() -> VortexResult<()> {
405 let mut ctx = array_session().create_execution_ctx();
406 let core_storage = row_storage([1, 2, 3])?;
407 let shredded = StructArray::try_from_iter([
408 (
409 "value",
410 PrimitiveArray::from_iter([10i32, 20, 30]).into_array(),
411 ),
412 (
413 "typed_value",
414 PrimitiveArray::from_iter([40i32, 50, 60]).into_array(),
415 ),
416 ])?;
417 let variant = VariantArray::try_new(core_storage, Some(shredded.into_array()))?;
418
419 let value_expr = variant_get(
420 root(),
421 VariantPath::field("value"),
422 Some(DType::Primitive(PType::I32, Nullability::NonNullable)),
423 );
424 let value_result = variant
425 .clone()
426 .into_array()
427 .apply(&value_expr)?
428 .execute::<PrimitiveArray>(&mut array_session().create_execution_ctx())?;
429 assert_arrays_eq!(
430 value_result,
431 PrimitiveArray::from_option_iter([Some(10i32), Some(20), Some(30)]),
432 &mut ctx
433 );
434
435 let typed_value_expr = variant_get(
436 root(),
437 VariantPath::field("typed_value"),
438 Some(DType::Primitive(PType::I32, Nullability::NonNullable)),
439 );
440 let typed_value_result = variant
441 .into_array()
442 .apply(&typed_value_expr)?
443 .execute::<PrimitiveArray>(&mut array_session().create_execution_ctx())?;
444 assert_arrays_eq!(
445 typed_value_result,
446 PrimitiveArray::from_option_iter([Some(40i32), Some(50), Some(60)]),
447 &mut ctx
448 );
449 Ok(())
450 }
451}