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