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 expected_shredded_array = if let Some(values) = expected_shredded
159 .iter()
160 .copied()
161 .collect::<Option<Vec<_>>>()
162 {
163 PrimitiveArray::from_iter(values)
164 } else {
165 PrimitiveArray::from_option_iter(expected_shredded.iter().copied())
166 };
167 assert_arrays_eq!(shredded, expected_shredded_array, &mut ctx);
168
169 Ok(())
170 }
171
172 fn assert_variant_core_rows(
173 array: &VariantArray,
174 expected_core: &[Option<i32>],
175 ) -> VortexResult<()> {
176 assert_eq!(array.len(), expected_core.len());
177
178 let mut ctx = array_session().create_execution_ctx();
179 for (idx, expected) in expected_core.iter().enumerate() {
180 let scalar = array.core_storage().execute_scalar(idx, &mut ctx)?;
181 let variant = scalar.as_variant();
182 match expected {
183 Some(expected) => {
184 let value = variant
185 .value()
186 .ok_or_else(|| vortex_err!("expected non-null variant row"))?;
187 assert_eq!(value.as_primitive().typed_value::<i32>(), Some(*expected));
188 }
189 None => assert!(variant.is_null()),
190 }
191 }
192
193 Ok(())
194 }
195
196 #[test]
197 fn try_new_exposes_core_storage_without_shredded() -> VortexResult<()> {
198 let core_storage = core_storage(2);
199
200 let variant = VariantArray::try_new(core_storage.clone(), None)?;
201
202 assert_eq!(variant.dtype(), core_storage.dtype());
203 assert_eq!(variant.len(), 2);
204 assert_eq!(variant.core_storage().dtype(), core_storage.dtype());
205 assert!(variant.shredded().is_none());
206 assert_variant_core_rows(&variant, &[Some(1), Some(1)])?;
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 assert_variant_rows(&variant, &[Some(1); 3], &[Some(10), Some(20), Some(30)])?;
233
234 Ok(())
235 }
236
237 #[test]
238 fn try_new_rejects_non_variant_core_storage() {
239 let core_storage = PrimitiveArray::from_iter([1i32, 2, 3]).into_array();
240
241 assert!(VariantArray::try_new(core_storage, None).is_err());
242 }
243
244 #[test]
245 fn try_new_rejects_shredded_length_mismatch() {
246 let core_storage = core_storage(3);
247 let shredded = buffer![10i32, 20].into_array();
248
249 assert!(VariantArray::try_new(core_storage, Some(shredded)).is_err());
250 }
251
252 #[test]
253 fn scalar_at_merges_shredded_with_core_storage() -> VortexResult<()> {
254 let dtype = DType::Variant(Nullability::Nullable);
255 let core_chunks = [Some(1i32), None, Some(3)]
256 .into_iter()
257 .map(|value| {
258 let scalar = match value {
259 Some(value) => {
260 Scalar::variant(Scalar::primitive(value, Nullability::NonNullable))
261 .cast(&dtype)?
262 }
263 None => Scalar::null(dtype.clone()),
264 };
265 Ok(ConstantArray::new(scalar, 1).into_array())
266 })
267 .collect::<VortexResult<Vec<_>>>()?;
268 let core_storage = ChunkedArray::try_new(core_chunks, dtype)?.into_array();
269 let shredded = PrimitiveArray::from_option_iter([Some(10i32), Some(20), None]).into_array();
270 let variant = VariantArray::try_new(core_storage, Some(shredded))?;
271
272 let mut ctx = array_session().create_execution_ctx();
273 for (idx, expected) in [Some(10i32), None, Some(3)].into_iter().enumerate() {
274 let scalar = variant.execute_scalar(idx, &mut ctx)?;
275 let variant = scalar.as_variant();
276 match expected {
277 Some(expected) => {
278 let value = variant
279 .value()
280 .ok_or_else(|| vortex_err!("expected non-null variant row"))?;
281 assert_eq!(value.as_primitive().typed_value::<i32>(), Some(expected));
282 }
283 None => assert!(variant.is_null()),
284 }
285 }
286
287 Ok(())
288 }
289
290 #[test]
291 fn slice_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
292 let variant = variant_with_shredded(0..5, 10..15)?;
293
294 let sliced = execute_variant(variant.into_array().slice(1..4)?)?;
295
296 assert_variant_rows(
297 &sliced,
298 &[Some(1), Some(2), Some(3)],
299 &[Some(11), Some(12), Some(13)],
300 )
301 }
302
303 #[test]
304 fn filter_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
305 let variant = variant_with_shredded(0..5, 10..15)?;
306
307 let filtered = execute_variant(
308 variant
309 .into_array()
310 .filter(Mask::from_iter([true, false, true, false, true]))?,
311 )?;
312
313 assert_variant_rows(
314 &filtered,
315 &[Some(0), Some(2), Some(4)],
316 &[Some(10), Some(12), Some(14)],
317 )
318 }
319
320 #[test]
321 fn take_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
322 let variant = variant_with_shredded(0..5, 10..15)?;
323
324 let taken = execute_variant(
325 variant
326 .into_array()
327 .take(buffer![4u64, 1, 3].into_array())?,
328 )?;
329
330 assert_variant_rows(
331 &taken,
332 &[Some(4), Some(1), Some(3)],
333 &[Some(14), Some(11), Some(13)],
334 )
335 }
336
337 #[test]
338 fn mask_preserves_core_storage_and_shredded_rows() -> VortexResult<()> {
339 let variant = variant_with_shredded(0..5, 10..15)?;
340 let mask = BoolArray::from_iter([true, false, true, false, true]).into_array();
341
342 let masked = execute_variant(variant.into_array().mask(mask)?)?;
343
344 assert_variant_rows(
345 &masked,
346 &[Some(0), None, Some(2), None, Some(4)],
347 &[Some(10), None, Some(12), None, Some(14)],
348 )
349 }
350
351 #[test]
352 fn mask_preserves_chunked_core_storage_validity() -> VortexResult<()> {
353 let dtype = DType::Variant(Nullability::Nullable);
354 let core_chunks = [Some(1i32), None, Some(3), Some(4)]
355 .into_iter()
356 .map(|value| {
357 let scalar = match value {
358 Some(value) => {
359 Scalar::variant(Scalar::primitive(value, Nullability::NonNullable))
360 .cast(&dtype)?
361 }
362 None => Scalar::null(dtype.clone()),
363 };
364 Ok(ConstantArray::new(scalar, 1).into_array())
365 })
366 .collect::<VortexResult<Vec<_>>>()?;
367 let core_storage = ChunkedArray::try_new(core_chunks, dtype)?.into_array();
368 let variant = VariantArray::try_new(core_storage, None)?;
369 let mask = BoolArray::from_iter([true, true, false, true]).into_array();
370
371 let masked = execute_variant(variant.into_array().mask(mask)?)?;
372
373 assert_variant_core_rows(&masked, &[Some(1), None, None, Some(4)])
374 }
375
376 #[test]
377 fn variant_get_keeps_valid_shredded_rows_for_matching_dtype() -> VortexResult<()> {
378 let mut ctx = array_session().create_execution_ctx();
379 let core_storage = row_storage([1, 2, 3])?;
380 let shredded = StructArray::try_from_iter([(
381 "a",
382 PrimitiveArray::from_iter([10i32, 20, 30]).into_array(),
383 )])?;
384 let variant = VariantArray::try_new(core_storage, Some(shredded.into_array()))?;
385 let expr = variant_get(
386 root(),
387 VariantPath::field("a"),
388 Some(DType::Primitive(PType::I32, Nullability::NonNullable)),
389 );
390
391 let result = variant.into_array().apply(&expr)?;
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.clone().into_array().apply(&value_expr)?;
423 assert_arrays_eq!(
424 value_result,
425 PrimitiveArray::from_option_iter([Some(10i32), Some(20), Some(30)]),
426 &mut ctx
427 );
428
429 let typed_value_expr = variant_get(
430 root(),
431 VariantPath::field("typed_value"),
432 Some(DType::Primitive(PType::I32, Nullability::NonNullable)),
433 );
434 let typed_value_result = variant.into_array().apply(&typed_value_expr)?;
435 assert_arrays_eq!(
436 typed_value_result,
437 PrimitiveArray::from_option_iter([Some(40i32), Some(50), Some(60)]),
438 &mut ctx
439 );
440 Ok(())
441 }
442}