Skip to main content

vortex_array/
canonical.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Encodings that enable zero-copy sharing of data with Arrow.
5
6use std::sync::Arc;
7
8use vortex_buffer::BitBuffer;
9use vortex_buffer::Buffer;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_ensure;
13use vortex_error::vortex_panic;
14
15use crate::ArrayRef;
16use crate::ArraySlots;
17use crate::Executable;
18use crate::ExecutionCtx;
19use crate::IntoArray;
20use crate::array::ArrayView;
21use crate::array::child_to_validity;
22use crate::arrays::Bool;
23use crate::arrays::BoolArray;
24use crate::arrays::Decimal;
25use crate::arrays::DecimalArray;
26use crate::arrays::Extension;
27use crate::arrays::ExtensionArray;
28use crate::arrays::FixedSizeList;
29use crate::arrays::FixedSizeListArray;
30use crate::arrays::ListView;
31use crate::arrays::ListViewArray;
32use crate::arrays::Map;
33use crate::arrays::MapArray;
34use crate::arrays::Null;
35use crate::arrays::NullArray;
36use crate::arrays::Primitive;
37use crate::arrays::PrimitiveArray;
38use crate::arrays::Struct;
39use crate::arrays::StructArray;
40use crate::arrays::Union;
41use crate::arrays::UnionArray;
42use crate::arrays::VarBinView;
43use crate::arrays::VarBinViewArray;
44use crate::arrays::Variant;
45use crate::arrays::VariantArray;
46use crate::arrays::bool::BoolDataParts;
47use crate::arrays::decimal::DecimalDataParts;
48use crate::arrays::extension::ExtensionArrayExt;
49use crate::arrays::fixed_size_list::FixedSizeListArrayExt;
50use crate::arrays::listview::ListViewDataParts;
51use crate::arrays::listview::ListViewRebuildMode;
52use crate::arrays::map::MapArrayExt;
53use crate::arrays::map::MapArraySlotsExt;
54use crate::arrays::primitive::PrimitiveDataParts;
55use crate::arrays::struct_::StructDataParts;
56use crate::arrays::union::UnionDataParts;
57use crate::arrays::varbinview::VarBinViewDataParts;
58use crate::arrays::variant::VariantArraySlotsExt;
59use crate::dtype::DType;
60use crate::dtype::NativePType;
61use crate::dtype::Nullability;
62use crate::dtype::PType;
63use crate::match_each_decimal_value_type;
64use crate::match_each_native_ptype;
65use crate::matcher::Matcher;
66use crate::validity::Validity;
67
68/// An enum capturing the default uncompressed encodings for each [Vortex type](DType).
69///
70/// Any array can be decoded into canonical form via the `to_canonical`
71/// trait method. This is the simplest encoding for a type, and will not be compressed but may
72/// contain compressed child arrays.
73///
74/// Canonical form is useful for doing type-specific compute where you need to know that all
75/// elements are laid out decompressed and contiguous in memory.
76///
77/// Each `Canonical` variant has a corresponding [`DType`] variant, with the notable exception of
78/// [`Canonical::VarBinView`], which is the canonical encoding for both [`DType::Utf8`] and
79/// [`DType::Binary`].
80///
81/// # Laziness
82///
83/// Canonical form is not recursive, so while a `StructArray` is the canonical format for any
84/// `Struct` type, individual column child arrays may still be compressed. This allows
85/// compute over Vortex arrays to push decoding as late as possible, and ideally many child arrays
86/// never need to be decoded into canonical form at all depending on the compute.
87///
88/// # Arrow interoperability
89///
90/// Most Vortex canonical encodings have an equivalent Arrow encoding that can be built zero-copy,
91/// and the corresponding Arrow array types can also be built directly. Map array Arrow transport is
92/// not implemented yet, and [`UnionArray`]'s independent top-level validity cannot be represented
93/// directly by an Arrow union.
94///
95/// The full list of canonical types and their equivalent Arrow array types are:
96///
97/// * `NullArray`: `arrow_array::NullArray`
98/// * `BoolArray`: `arrow_array::BooleanArray`
99/// * `PrimitiveArray`: `arrow_array::PrimitiveArray`
100/// * `DecimalArray`: `arrow_array::Decimal128Array` and `arrow_array::Decimal256Array`
101/// * `VarBinViewArray`: `arrow_array::GenericByteViewArray`
102/// * `ListViewArray`: `arrow_array::ListViewArray`
103/// * `MapArray`: Vortex `ListView<Struct<key, value>>` storage
104/// * `FixedSizeListArray`: `arrow_array::FixedSizeListArray`
105/// * `StructArray`: `arrow_array::StructArray`
106///
107/// Vortex uses a logical type system, unlike Arrow which uses physical encodings for its types.
108/// As an example, there are at least six valid physical encodings for a `Utf8` array. This can
109/// create ambiguity.
110/// Thus, if you receive an Arrow array, compress it using Vortex, and then
111/// decompress it later to pass to a compute kernel, there are multiple suitable Arrow array
112/// variants to hold the data.
113///
114/// To disambiguate, we choose a canonical physical encoding for every Vortex [`DType`], which
115/// will correspond to an arrow-rs `arrow_schema::DataType`.
116///
117/// # Views support
118///
119/// Binary and String views, also known as "German strings" are a better encoding format for
120/// nearly all use-cases. Variable-length binary views are part of the Apache Arrow spec, and are
121/// fully supported by the Datafusion query engine. We use them as our canonical string encoding
122/// for all `Utf8` and `Binary` typed arrays in Vortex. They provide considerably faster filter
123/// execution than the core `StringArray` and `BinaryArray` types, at the expense of potentially
124/// needing garbage collection (`arrow_array::GenericByteViewArray::gc`) to clear unreferenced items
125/// from memory.
126///
127/// # For Developers
128///
129/// If you add another variant to this enum, make sure to update `dyn Array::is_canonical`,
130/// and the fuzzer in `fuzz/fuzz_targets/array_ops.rs`.
131#[derive(Debug, Clone)]
132pub enum Canonical {
133    Null(NullArray),
134    Bool(BoolArray),
135    Primitive(PrimitiveArray),
136    Decimal(DecimalArray),
137    VarBinView(VarBinViewArray),
138    List(ListViewArray),
139    Map(MapArray),
140    FixedSizeList(FixedSizeListArray),
141    Struct(StructArray),
142    Union(UnionArray),
143    /// Canonical storage for extension dtypes, wrapping the canonical form of the storage dtype.
144    Extension(ExtensionArray),
145    /// Canonical storage for dynamic variant values, optionally with typed shredded paths.
146    Variant(VariantArray),
147}
148
149/// Match on every canonical variant and evaluate a code block on all variants
150macro_rules! match_each_canonical {
151    ($self:expr, | $ident:ident | $eval:expr) => {{
152        match $self {
153            Canonical::Null($ident) => $eval,
154            Canonical::Bool($ident) => $eval,
155            Canonical::Primitive($ident) => $eval,
156            Canonical::Decimal($ident) => $eval,
157            Canonical::VarBinView($ident) => $eval,
158            Canonical::List($ident) => $eval,
159            Canonical::Map($ident) => $eval,
160            Canonical::FixedSizeList($ident) => $eval,
161            Canonical::Struct($ident) => $eval,
162            Canonical::Union($ident) => $eval,
163            Canonical::Variant($ident) => $eval,
164            Canonical::Extension($ident) => $eval,
165        }
166    }};
167}
168
169impl Canonical {
170    /// Create an empty canonical array of the given dtype.
171    pub fn empty(dtype: &DType) -> Canonical {
172        match dtype {
173            DType::Null => Canonical::Null(NullArray::new(0)),
174            DType::Bool(n) => Canonical::Bool(unsafe {
175                BoolArray::new_unchecked(BitBuffer::empty(), Validity::from(n))
176            }),
177            DType::Primitive(ptype, n) => {
178                match_each_native_ptype!(ptype, |P| {
179                    Canonical::Primitive(unsafe {
180                        PrimitiveArray::new_unchecked(Buffer::<P>::empty(), Validity::from(n))
181                    })
182                })
183            }
184            DType::Decimal(decimal_type, n) => {
185                match_each_decimal_value_type!(
186                    DecimalType::smallest_decimal_value_type(decimal_type),
187                    |D| {
188                        Canonical::Decimal(unsafe {
189                            DecimalArray::new_unchecked::<D>(
190                                Buffer::empty(),
191                                *decimal_type,
192                                Validity::from(n),
193                            )
194                        })
195                    }
196                )
197            }
198            DType::Utf8(n) => Canonical::VarBinView(unsafe {
199                VarBinViewArray::new_unchecked(
200                    Buffer::empty(),
201                    Arc::new([]),
202                    dtype.clone(),
203                    Validity::from(n),
204                )
205            }),
206            DType::Binary(n) => Canonical::VarBinView(unsafe {
207                VarBinViewArray::new_unchecked(
208                    Buffer::empty(),
209                    Arc::new([]),
210                    dtype.clone(),
211                    Validity::from(n),
212                )
213            }),
214            DType::List(dtype, n) => Canonical::List(unsafe {
215                ListViewArray::new_unchecked(
216                    Canonical::empty(dtype).into_array(),
217                    Canonical::empty(&DType::Primitive(PType::U8, Nullability::NonNullable))
218                        .into_array(),
219                    Canonical::empty(&DType::Primitive(PType::U8, Nullability::NonNullable))
220                        .into_array(),
221                    Validity::from(n),
222                )
223                // An empty list view is trivially copyable to a list.
224                .with_zero_copy_to_list(true)
225            }),
226            DType::Map(map_dtype, nullability) => Canonical::Map(MapArray::new(
227                map_dtype.clone(),
228                Canonical::empty(&DType::List(
229                    Arc::new(map_dtype.entries_dtype()),
230                    *nullability,
231                ))
232                .into_listview(),
233            )),
234            DType::FixedSizeList(elem_dtype, list_size, null) => Canonical::FixedSizeList(unsafe {
235                FixedSizeListArray::new_unchecked(
236                    Canonical::empty(elem_dtype).into_array(),
237                    *list_size,
238                    Validity::from(null),
239                    0,
240                )
241            }),
242            DType::Struct(struct_dtype, n) => Canonical::Struct(unsafe {
243                StructArray::new_unchecked(
244                    struct_dtype
245                        .fields()
246                        .map(|f| Canonical::empty(&f).into_array())
247                        .collect::<Vec<_>>(),
248                    struct_dtype.clone(),
249                    0,
250                    Validity::from(n),
251                )
252            }),
253            DType::Union(variants, nullability) => {
254                Canonical::Union(UnionArray::empty(variants.clone(), *nullability))
255            }
256            DType::Variant(_) => {
257                vortex_panic!(InvalidArgument: "Canonical empty is not supported for Variant")
258            }
259            DType::Extension(ext_dtype) => Canonical::Extension(ExtensionArray::new(
260                ext_dtype.clone(),
261                Canonical::empty(ext_dtype.storage_dtype()).into_array(),
262            )),
263        }
264    }
265
266    pub fn len(&self) -> usize {
267        match_each_canonical!(self, |arr| arr.len())
268    }
269
270    pub fn dtype(&self) -> &DType {
271        match_each_canonical!(self, |arr| arr.dtype())
272    }
273
274    pub fn is_empty(&self) -> bool {
275        match_each_canonical!(self, |arr| arr.is_empty())
276    }
277}
278
279impl Canonical {
280    /// Performs a (potentially expensive) compaction operation on the array before it is complete.
281    ///
282    /// This is mostly relevant for the variable-length types such as Utf8, Binary or List where
283    /// they can accumulate wasted space after slicing and taking operations.
284    ///
285    /// This operation can be very expensive and can result in things like allocations, full-scans
286    /// and copy operations.
287    pub fn compact(&self, ctx: &mut ExecutionCtx) -> VortexResult<Canonical> {
288        match self {
289            Canonical::Bool(array) => Ok(Canonical::Bool(array.trim_bits()?)),
290            Canonical::VarBinView(array) => Ok(Canonical::VarBinView(array.compact_buffers(ctx)?)),
291            Canonical::List(array) => Ok(Canonical::List(
292                array.rebuild(ListViewRebuildMode::TrimElements, ctx)?,
293            )),
294            Canonical::Map(array) => Ok(Canonical::Map(MapArray::new(
295                array.map_dtype().clone(),
296                array
297                    .entries()
298                    .as_::<ListView>()
299                    .into_owned()
300                    .rebuild(ListViewRebuildMode::TrimElements, ctx)?,
301            ))),
302            _ => Ok(self.clone()),
303        }
304    }
305}
306
307// Unwrap canonical type back down to specialized type.
308impl Canonical {
309    pub fn as_null(&self) -> &NullArray {
310        if let Canonical::Null(a) = self {
311            a
312        } else {
313            vortex_panic!("Cannot get NullArray from {:?}", &self)
314        }
315    }
316
317    pub fn into_null(self) -> NullArray {
318        if let Canonical::Null(a) = self {
319            a
320        } else {
321            vortex_panic!("Cannot unwrap NullArray from {:?}", &self)
322        }
323    }
324
325    pub fn as_bool(&self) -> &BoolArray {
326        if let Canonical::Bool(a) = self {
327            a
328        } else {
329            vortex_panic!("Cannot get BoolArray from {:?}", &self)
330        }
331    }
332
333    pub fn into_bool(self) -> BoolArray {
334        if let Canonical::Bool(a) = self {
335            a
336        } else {
337            vortex_panic!("Cannot unwrap BoolArray from {:?}", &self)
338        }
339    }
340
341    pub fn as_primitive(&self) -> &PrimitiveArray {
342        if let Canonical::Primitive(a) = self {
343            a
344        } else {
345            vortex_panic!("Cannot get PrimitiveArray from {:?}", &self)
346        }
347    }
348
349    pub fn into_primitive(self) -> PrimitiveArray {
350        if let Canonical::Primitive(a) = self {
351            a
352        } else {
353            vortex_panic!("Cannot unwrap PrimitiveArray from {:?}", &self)
354        }
355    }
356
357    pub fn as_decimal(&self) -> &DecimalArray {
358        if let Canonical::Decimal(a) = self {
359            a
360        } else {
361            vortex_panic!("Cannot get DecimalArray from {:?}", &self)
362        }
363    }
364
365    pub fn into_decimal(self) -> DecimalArray {
366        if let Canonical::Decimal(a) = self {
367            a
368        } else {
369            vortex_panic!("Cannot unwrap DecimalArray from {:?}", &self)
370        }
371    }
372
373    pub fn as_varbinview(&self) -> &VarBinViewArray {
374        if let Canonical::VarBinView(a) = self {
375            a
376        } else {
377            vortex_panic!("Cannot get VarBinViewArray from {:?}", &self)
378        }
379    }
380
381    pub fn into_varbinview(self) -> VarBinViewArray {
382        if let Canonical::VarBinView(a) = self {
383            a
384        } else {
385            vortex_panic!("Cannot unwrap VarBinViewArray from {:?}", &self)
386        }
387    }
388
389    pub fn as_listview(&self) -> &ListViewArray {
390        if let Canonical::List(a) = self {
391            a
392        } else {
393            vortex_panic!("Cannot get ListArray from {:?}", &self)
394        }
395    }
396
397    pub fn into_listview(self) -> ListViewArray {
398        if let Canonical::List(a) = self {
399            a
400        } else {
401            vortex_panic!("Cannot unwrap ListArray from {:?}", &self)
402        }
403    }
404
405    pub fn as_map(&self) -> &MapArray {
406        if let Canonical::Map(a) = self {
407            a
408        } else {
409            vortex_panic!("Cannot get MapArray from {:?}", &self)
410        }
411    }
412
413    pub fn into_map(self) -> MapArray {
414        if let Canonical::Map(a) = self {
415            a
416        } else {
417            vortex_panic!("Cannot unwrap MapArray from {:?}", &self)
418        }
419    }
420
421    pub fn as_fixed_size_list(&self) -> &FixedSizeListArray {
422        if let Canonical::FixedSizeList(a) = self {
423            a
424        } else {
425            vortex_panic!("Cannot get FixedSizeListArray from {:?}", &self)
426        }
427    }
428
429    pub fn into_fixed_size_list(self) -> FixedSizeListArray {
430        if let Canonical::FixedSizeList(a) = self {
431            a
432        } else {
433            vortex_panic!("Cannot unwrap FixedSizeListArray from {:?}", &self)
434        }
435    }
436
437    pub fn as_struct(&self) -> &StructArray {
438        if let Canonical::Struct(a) = self {
439            a
440        } else {
441            vortex_panic!("Cannot get StructArray from {:?}", &self)
442        }
443    }
444
445    pub fn into_struct(self) -> StructArray {
446        if let Canonical::Struct(a) = self {
447            a
448        } else {
449            vortex_panic!("Cannot unwrap StructArray from {:?}", &self)
450        }
451    }
452
453    /// Return this canonical array as a sparse [`UnionArray`].
454    pub fn as_union(&self) -> &UnionArray {
455        if let Canonical::Union(a) = self {
456            a
457        } else {
458            vortex_panic!("Cannot get UnionArray from {:?}", &self)
459        }
460    }
461
462    /// Unwrap this canonical array as a sparse [`UnionArray`].
463    pub fn into_union(self) -> UnionArray {
464        if let Canonical::Union(a) = self {
465            a
466        } else {
467            vortex_panic!("Cannot unwrap UnionArray from {:?}", &self)
468        }
469    }
470
471    pub fn as_extension(&self) -> &ExtensionArray {
472        if let Canonical::Extension(a) = self {
473            a
474        } else {
475            vortex_panic!("Cannot get ExtensionArray from {:?}", &self)
476        }
477    }
478
479    pub fn into_extension(self) -> ExtensionArray {
480        if let Canonical::Extension(a) = self {
481            a
482        } else {
483            vortex_panic!("Cannot unwrap ExtensionArray from {:?}", &self)
484        }
485    }
486}
487
488impl IntoArray for Canonical {
489    fn into_array(self) -> ArrayRef {
490        match_each_canonical!(self, |arr| arr.into_array())
491    }
492}
493
494/// Trait for types that can be converted from an owned type into an owned array variant.
495///
496/// # Canonicalization
497///
498/// This trait has a blanket implementation for all types implementing [ToCanonical].
499#[deprecated(note = "use `array.execute::<T>(ctx)` instead")]
500pub trait ToCanonical {
501    /// Canonicalize into a [`NullArray`] if the target is [`Null`](DType::Null) typed.
502    #[deprecated(note = "use `array.execute::<NullArray>(ctx)` instead")]
503    fn to_null(&self) -> NullArray;
504
505    /// Canonicalize into a [`BoolArray`] if the target is [`Bool`](DType::Bool) typed.
506    #[deprecated(note = "use `array.execute::<BoolArray>(ctx)` instead")]
507    fn to_bool(&self) -> BoolArray;
508
509    /// Canonicalize into a [`PrimitiveArray`] if the target is [`Primitive`](DType::Primitive)
510    /// typed.
511    #[deprecated(note = "use `array.execute::<PrimitiveArray>(ctx)` instead")]
512    fn to_primitive(&self) -> PrimitiveArray;
513
514    /// Canonicalize into a [`DecimalArray`] if the target is [`Decimal`](DType::Decimal)
515    /// typed.
516    #[deprecated(note = "use `array.execute::<DecimalArray>(ctx)` instead")]
517    fn to_decimal(&self) -> DecimalArray;
518
519    /// Canonicalize into a [`StructArray`] if the target is [`Struct`](DType::Struct) typed.
520    #[deprecated(note = "use `array.execute::<StructArray>(ctx)` instead")]
521    fn to_struct(&self) -> StructArray;
522
523    /// Canonicalize into a [`ListViewArray`] if the target is [`List`](DType::List) typed.
524    #[deprecated(note = "use `array.execute::<ListViewArray>(ctx)` instead")]
525    fn to_listview(&self) -> ListViewArray;
526
527    /// Canonicalize into a [`MapArray`] if the target is [`Map`](DType::Map) typed.
528    #[deprecated(note = "use `array.execute::<MapArray>(ctx)` instead")]
529    fn to_map(&self) -> MapArray;
530
531    /// Canonicalize into a [`FixedSizeListArray`] if the target is [`List`](DType::FixedSizeList)
532    /// typed.
533    #[deprecated(note = "use `array.execute::<FixedSizeListArray>(ctx)` instead")]
534    fn to_fixed_size_list(&self) -> FixedSizeListArray;
535
536    /// Canonicalize into a [`VarBinViewArray`] if the target is [`Utf8`](DType::Utf8)
537    /// or [`Binary`](DType::Binary) typed.
538    #[deprecated(note = "use `array.execute::<VarBinViewArray>(ctx)` instead")]
539    fn to_varbinview(&self) -> VarBinViewArray;
540
541    /// Canonicalize into an [`ExtensionArray`] if the array is [`Extension`](DType::Extension)
542    /// typed.
543    #[deprecated(note = "use `array.execute::<ExtensionArray>(ctx)` instead")]
544    fn to_extension(&self) -> ExtensionArray;
545}
546
547// Blanket impl for all Array encodings.
548#[expect(deprecated)]
549impl ToCanonical for ArrayRef {
550    fn to_null(&self) -> NullArray {
551        #[expect(deprecated)]
552        let result = self.to_canonical().vortex_expect("to_canonical failed");
553        result.into_null()
554    }
555
556    fn to_bool(&self) -> BoolArray {
557        #[expect(deprecated)]
558        let result = self.to_canonical().vortex_expect("to_canonical failed");
559        result.into_bool()
560    }
561
562    fn to_primitive(&self) -> PrimitiveArray {
563        #[expect(deprecated)]
564        let result = self.to_canonical().vortex_expect("to_canonical failed");
565        result.into_primitive()
566    }
567
568    fn to_decimal(&self) -> DecimalArray {
569        #[expect(deprecated)]
570        let result = self.to_canonical().vortex_expect("to_canonical failed");
571        result.into_decimal()
572    }
573
574    fn to_struct(&self) -> StructArray {
575        #[expect(deprecated)]
576        let result = self.to_canonical().vortex_expect("to_canonical failed");
577        result.into_struct()
578    }
579
580    fn to_listview(&self) -> ListViewArray {
581        #[expect(deprecated)]
582        let result = self.to_canonical().vortex_expect("to_canonical failed");
583        result.into_listview()
584    }
585
586    fn to_map(&self) -> MapArray {
587        #[expect(deprecated)]
588        let result = self.to_canonical().vortex_expect("to_canonical failed");
589        result.into_map()
590    }
591
592    fn to_fixed_size_list(&self) -> FixedSizeListArray {
593        #[expect(deprecated)]
594        let result = self.to_canonical().vortex_expect("to_canonical failed");
595        result.into_fixed_size_list()
596    }
597
598    fn to_varbinview(&self) -> VarBinViewArray {
599        #[expect(deprecated)]
600        let result = self.to_canonical().vortex_expect("to_canonical failed");
601        result.into_varbinview()
602    }
603
604    fn to_extension(&self) -> ExtensionArray {
605        #[expect(deprecated)]
606        let result = self.to_canonical().vortex_expect("to_canonical failed");
607        result.into_extension()
608    }
609}
610
611impl From<Canonical> for ArrayRef {
612    fn from(value: Canonical) -> Self {
613        match_each_canonical!(value, |arr| arr.into_array())
614    }
615}
616
617/// Execute into [`Canonical`] by running `execute_until` with the [`AnyCanonical`] matcher.
618///
619/// Unlike executing into [`crate::Columnar`], this will fully expand constant arrays into their
620/// canonical form. Callers should prefer to execute into `Columnar` if they are able to optimize
621/// their use for constant arrays.
622impl Executable for Canonical {
623    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
624        let result = array.execute_until::<AnyCanonical>(ctx)?;
625        Ok(result
626            .as_opt::<AnyCanonical>()
627            .map(Canonical::from)
628            .vortex_expect("execute_until::<AnyCanonical> must return a canonical array"))
629    }
630}
631
632/// Recursively execute the array until it reaches canonical form along with its validity.
633///
634/// Callers should prefer to execute into `Columnar` instead of this specific target.
635/// This target is useful when preparing arrays for writing.
636pub struct CanonicalValidity(pub Canonical);
637
638impl Executable for CanonicalValidity {
639    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
640        match array.execute::<Canonical>(ctx)? {
641            n @ Canonical::Null(_) => Ok(CanonicalValidity(n)),
642            Canonical::Bool(b) => {
643                let validity = child_to_validity(b.slots()[0].as_ref(), b.dtype().nullability());
644                let len = b.len();
645                let BoolDataParts { bits, meta } = b.into_data().into_parts(len);
646                Ok(CanonicalValidity(Canonical::Bool(
647                    BoolArray::try_new_from_handle(
648                        bits,
649                        meta.offset(),
650                        meta.len(),
651                        validity.execute(ctx)?,
652                    )?,
653                )))
654            }
655            Canonical::Primitive(p) => {
656                let PrimitiveDataParts {
657                    ptype,
658                    buffer,
659                    validity,
660                } = p.into_data_parts();
661                Ok(CanonicalValidity(Canonical::Primitive(unsafe {
662                    PrimitiveArray::new_unchecked_from_handle(buffer, ptype, validity.execute(ctx)?)
663                })))
664            }
665            Canonical::Decimal(d) => {
666                let DecimalDataParts {
667                    decimal_dtype,
668                    values,
669                    values_type,
670                    validity,
671                } = d.into_data_parts();
672                Ok(CanonicalValidity(Canonical::Decimal(unsafe {
673                    DecimalArray::new_unchecked_handle(
674                        values,
675                        values_type,
676                        decimal_dtype,
677                        validity.execute(ctx)?,
678                    )
679                })))
680            }
681            Canonical::VarBinView(vbv) => {
682                let VarBinViewDataParts {
683                    dtype,
684                    buffers,
685                    views,
686                    validity,
687                } = vbv.into_data_parts();
688                Ok(CanonicalValidity(Canonical::VarBinView(unsafe {
689                    VarBinViewArray::new_handle_unchecked(
690                        views,
691                        buffers,
692                        dtype,
693                        validity.execute(ctx)?,
694                    )
695                })))
696            }
697            Canonical::List(l) => {
698                let zctl = l.is_zero_copy_to_list();
699                let ListViewDataParts {
700                    elements,
701                    offsets,
702                    sizes,
703                    validity,
704                    ..
705                } = l.into_data_parts();
706                Ok(CanonicalValidity(Canonical::List(unsafe {
707                    ListViewArray::new_unchecked(elements, offsets, sizes, validity.execute(ctx)?)
708                        .with_zero_copy_to_list(zctl)
709                })))
710            }
711            Canonical::Map(map) => {
712                let map_dtype = map.map_dtype().clone();
713                let entries = map.entries().clone();
714                Ok(CanonicalValidity(Canonical::Map(MapArray::new(
715                    map_dtype,
716                    entries.execute::<CanonicalValidity>(ctx)?.0.into_listview(),
717                ))))
718            }
719            Canonical::FixedSizeList(fsl) => {
720                let list_size = fsl.list_size();
721                let len = fsl.len();
722                let parts = fsl.into_data_parts();
723                let elements = parts.elements;
724                let validity = parts.validity;
725                Ok(CanonicalValidity(Canonical::FixedSizeList(
726                    FixedSizeListArray::new(elements, list_size, validity.execute(ctx)?, len),
727                )))
728            }
729            Canonical::Struct(st) => {
730                let len = st.len();
731                let StructDataParts {
732                    struct_fields,
733                    fields,
734                    validity,
735                } = st.into_data_parts();
736                Ok(CanonicalValidity(Canonical::Struct(unsafe {
737                    StructArray::new_unchecked(fields, struct_fields, len, validity.execute(ctx)?)
738                })))
739            }
740            Canonical::Union(union) => {
741                let UnionDataParts {
742                    variants,
743                    type_ids,
744                    children,
745                } = union.into_data_parts();
746                let type_ids = type_ids.execute::<CanonicalValidity>(ctx)?.0.into_array();
747
748                Ok(CanonicalValidity(Canonical::Union(unsafe {
749                    UnionArray::new_unchecked(type_ids, variants, children.iter().cloned())
750                })))
751            }
752            Canonical::Extension(ext) => Ok(CanonicalValidity(Canonical::Extension(
753                ExtensionArray::new(
754                    ext.ext_dtype().clone(),
755                    ext.storage_array()
756                        .clone()
757                        .execute::<CanonicalValidity>(ctx)?
758                        .0
759                        .into_array(),
760                ),
761            ))),
762            Canonical::Variant(variant) => {
763                let core_storage = recursively_canonicalize_slots(variant.core_storage(), ctx)?;
764                let shredded = variant
765                    .shredded()
766                    .map(|shredded| {
767                        if shredded.is::<Variant>() {
768                            recursively_canonicalize_slots(shredded, ctx)
769                        } else {
770                            shredded
771                                .clone()
772                                .execute::<CanonicalValidity>(ctx)
773                                .map(|canonical| canonical.0.into_array())
774                        }
775                    })
776                    .transpose()?;
777                Ok(CanonicalValidity(Canonical::Variant(
778                    VariantArray::try_new(core_storage, shredded)?,
779                )))
780            }
781        }
782    }
783}
784
785/// Recursively execute the array until all of its children are canonical.
786///
787/// This method is useful to guarantee that all operators are fully executed,
788/// callers should prefer an execution target that's suitable for their use case instead of this one.
789pub struct RecursiveCanonical(pub Canonical);
790
791// TODO: Currently only used for Variant, in the future
792// can probably be used for more canonical types like Struct.
793fn recursively_canonicalize_slots(
794    array: &ArrayRef,
795    ctx: &mut ExecutionCtx,
796) -> VortexResult<ArrayRef> {
797    let slots = array
798        .slots()
799        .iter()
800        .map(|slot| {
801            slot.as_ref()
802                .map(|child| {
803                    child
804                        .clone()
805                        .execute::<RecursiveCanonical>(ctx)
806                        .map(|canonical| canonical.0.into_array())
807                })
808                .transpose()
809        })
810        .collect::<VortexResult<ArraySlots>>()?;
811    // SAFETY: recursive canonicalization rewrites child slots to equivalent canonical
812    // representations, preserving the parent array's logical values and statistics.
813    unsafe { array.clone().with_slots(slots) }
814}
815impl Executable for RecursiveCanonical {
816    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
817        match array.execute::<Canonical>(ctx)? {
818            n @ Canonical::Null(_) => Ok(RecursiveCanonical(n)),
819            Canonical::Bool(b) => {
820                let validity = child_to_validity(b.slots()[0].as_ref(), b.dtype().nullability());
821                let len = b.len();
822                let BoolDataParts { bits, meta } = b.into_data().into_parts(len);
823                Ok(RecursiveCanonical(Canonical::Bool(
824                    BoolArray::try_new_from_handle(
825                        bits,
826                        meta.offset(),
827                        meta.len(),
828                        validity.execute(ctx)?,
829                    )?,
830                )))
831            }
832            Canonical::Primitive(p) => {
833                let PrimitiveDataParts {
834                    ptype,
835                    buffer,
836                    validity,
837                } = p.into_data_parts();
838                Ok(RecursiveCanonical(Canonical::Primitive(unsafe {
839                    PrimitiveArray::new_unchecked_from_handle(buffer, ptype, validity.execute(ctx)?)
840                })))
841            }
842            Canonical::Decimal(d) => {
843                let DecimalDataParts {
844                    decimal_dtype,
845                    values,
846                    values_type,
847                    validity,
848                } = d.into_data_parts();
849                Ok(RecursiveCanonical(Canonical::Decimal(unsafe {
850                    DecimalArray::new_unchecked_handle(
851                        values,
852                        values_type,
853                        decimal_dtype,
854                        validity.execute(ctx)?,
855                    )
856                })))
857            }
858            Canonical::VarBinView(vbv) => {
859                let VarBinViewDataParts {
860                    dtype,
861                    buffers,
862                    views,
863                    validity,
864                } = vbv.into_data_parts();
865                Ok(RecursiveCanonical(Canonical::VarBinView(unsafe {
866                    VarBinViewArray::new_handle_unchecked(
867                        views,
868                        buffers,
869                        dtype,
870                        validity.execute(ctx)?,
871                    )
872                })))
873            }
874            Canonical::List(l) => {
875                let zctl = l.is_zero_copy_to_list();
876                let ListViewDataParts {
877                    elements,
878                    offsets,
879                    sizes,
880                    validity,
881                    ..
882                } = l.into_data_parts();
883                Ok(RecursiveCanonical(Canonical::List(unsafe {
884                    ListViewArray::new_unchecked(
885                        elements.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
886                        offsets.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
887                        sizes.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
888                        validity.execute(ctx)?,
889                    )
890                    .with_zero_copy_to_list(zctl)
891                })))
892            }
893            Canonical::Map(map) => {
894                let map_dtype = map.map_dtype().clone();
895                let entries = map.entries().clone();
896                Ok(RecursiveCanonical(Canonical::Map(MapArray::new(
897                    map_dtype,
898                    entries
899                        .execute::<RecursiveCanonical>(ctx)?
900                        .0
901                        .into_listview(),
902                ))))
903            }
904            Canonical::FixedSizeList(fsl) => {
905                let list_size = fsl.list_size();
906                let len = fsl.len();
907                let parts = fsl.into_data_parts();
908                let elements = parts.elements;
909                let validity = parts.validity;
910                Ok(RecursiveCanonical(Canonical::FixedSizeList(
911                    FixedSizeListArray::new(
912                        elements.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
913                        list_size,
914                        validity.execute(ctx)?,
915                        len,
916                    ),
917                )))
918            }
919            Canonical::Struct(st) => {
920                let len = st.len();
921                let StructDataParts {
922                    struct_fields,
923                    fields,
924                    validity,
925                } = st.into_data_parts();
926                let executed_fields = fields
927                    .into_iter()
928                    .map(|f| Ok(f.execute::<RecursiveCanonical>(ctx)?.0.into_array()))
929                    .collect::<VortexResult<Vec<_>>>()?;
930
931                Ok(RecursiveCanonical(Canonical::Struct(unsafe {
932                    StructArray::new_unchecked(
933                        executed_fields,
934                        struct_fields,
935                        len,
936                        validity.execute(ctx)?,
937                    )
938                })))
939            }
940            Canonical::Union(union) => {
941                let UnionDataParts {
942                    variants,
943                    type_ids,
944                    children,
945                } = union.into_data_parts();
946                let type_ids = type_ids.execute::<RecursiveCanonical>(ctx)?.0.into_array();
947                let children = children
948                    .iter()
949                    .cloned()
950                    .map(|child| {
951                        child
952                            .execute::<RecursiveCanonical>(ctx)
953                            .map(|canonical| canonical.0.into_array())
954                    })
955                    .collect::<VortexResult<Vec<_>>>()?;
956
957                Ok(RecursiveCanonical(Canonical::Union(unsafe {
958                    UnionArray::new_unchecked(type_ids, variants, children)
959                })))
960            }
961            Canonical::Extension(ext) => Ok(RecursiveCanonical(Canonical::Extension(
962                ExtensionArray::new(
963                    ext.ext_dtype().clone(),
964                    ext.storage_array()
965                        .clone()
966                        .execute::<RecursiveCanonical>(ctx)?
967                        .0
968                        .into_array(),
969                ),
970            ))),
971            Canonical::Variant(variant) => {
972                let core_storage = recursively_canonicalize_slots(variant.core_storage(), ctx)?;
973                let shredded = variant
974                    .shredded()
975                    .map(|shredded| {
976                        if shredded.is::<Variant>() {
977                            recursively_canonicalize_slots(shredded, ctx)
978                        } else {
979                            shredded
980                                .clone()
981                                .execute::<RecursiveCanonical>(ctx)
982                                .map(|canonical| canonical.0.into_array())
983                        }
984                    })
985                    .transpose()?;
986                Ok(RecursiveCanonical(Canonical::Variant(
987                    VariantArray::try_new(core_storage, shredded)?,
988                )))
989            }
990        }
991    }
992}
993
994/// Execute a primitive typed array into a buffer of native values, assuming all values are valid.
995///
996/// # Errors
997///
998/// Returns a `VortexError` if the array is not all-valid (has any nulls).
999impl<T: NativePType> Executable for Buffer<T> {
1000    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1001        let array = PrimitiveArray::execute(array, ctx)?;
1002        vortex_ensure!(
1003            matches!(
1004                array.validity()?,
1005                Validity::NonNullable | Validity::AllValid
1006            ),
1007            "Cannot execute to native buffer: array is not all-valid."
1008        );
1009        Ok(array.into_buffer())
1010    }
1011}
1012
1013/// Execute the array to canonical form and unwrap as a [`PrimitiveArray`].
1014///
1015/// This will panic if the array's dtype is not primitive.
1016impl Executable for PrimitiveArray {
1017    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1018        match array.try_downcast::<Primitive>() {
1019            Ok(primitive) => Ok(primitive),
1020            Err(array) => Ok(Canonical::execute(array, ctx)?.into_primitive()),
1021        }
1022    }
1023}
1024
1025/// Execute the array to canonical form and unwrap as a [`BoolArray`].
1026///
1027/// This will panic if the array's dtype is not bool.
1028impl Executable for BoolArray {
1029    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1030        match array.try_downcast::<Bool>() {
1031            Ok(bool_array) => Ok(bool_array),
1032            Err(array) => Ok(Canonical::execute(array, ctx)?.into_bool()),
1033        }
1034    }
1035}
1036
1037/// Execute the array to a [`BitBuffer`], aka a non-nullable  [`BoolArray`].
1038///
1039/// This will panic if the array's dtype is not non-nullable bool.
1040impl Executable for BitBuffer {
1041    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1042        let bool = BoolArray::execute(array, ctx)?;
1043        assert!(
1044            !bool.dtype().is_nullable(),
1045            "bit buffer execute only works with non-nullable bool arrays"
1046        );
1047        Ok(bool.into_bit_buffer())
1048    }
1049}
1050
1051/// Execute the array to canonical form and unwrap as a [`NullArray`].
1052///
1053/// This will panic if the array's dtype is not null.
1054impl Executable for NullArray {
1055    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1056        match array.try_downcast::<Null>() {
1057            Ok(null_array) => Ok(null_array),
1058            Err(array) => Ok(Canonical::execute(array, ctx)?.into_null()),
1059        }
1060    }
1061}
1062
1063/// Execute the array to canonical form and unwrap as a [`VarBinViewArray`].
1064///
1065/// This will panic if the array's dtype is not utf8 or binary.
1066impl Executable for VarBinViewArray {
1067    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1068        match array.try_downcast::<VarBinView>() {
1069            Ok(varbinview) => Ok(varbinview),
1070            Err(array) => Ok(Canonical::execute(array, ctx)?.into_varbinview()),
1071        }
1072    }
1073}
1074
1075/// Execute the array to canonical form and unwrap as an [`ExtensionArray`].
1076///
1077/// This will panic if the array's dtype is not an extension type.
1078impl Executable for ExtensionArray {
1079    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1080        match array.try_downcast::<Extension>() {
1081            Ok(ext_array) => Ok(ext_array),
1082            Err(array) => Ok(Canonical::execute(array, ctx)?.into_extension()),
1083        }
1084    }
1085}
1086
1087/// Execute the array to canonical form and unwrap as a [`DecimalArray`].
1088///
1089/// This will panic if the array's dtype is not decimal.
1090impl Executable for DecimalArray {
1091    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1092        match array.try_downcast::<Decimal>() {
1093            Ok(decimal) => Ok(decimal),
1094            Err(array) => Ok(Canonical::execute(array, ctx)?.into_decimal()),
1095        }
1096    }
1097}
1098
1099/// Execute the array to canonical form and unwrap as a [`ListViewArray`].
1100///
1101/// This will panic if the array's dtype is not list.
1102impl Executable for ListViewArray {
1103    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1104        match array.try_downcast::<ListView>() {
1105            Ok(list) => Ok(list),
1106            Err(array) => Ok(Canonical::execute(array, ctx)?.into_listview()),
1107        }
1108    }
1109}
1110
1111/// Execute the array to canonical form and unwrap as a [`MapArray`].
1112///
1113/// This will panic if the array's dtype is not map.
1114impl Executable for MapArray {
1115    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1116        match array.try_downcast::<Map>() {
1117            Ok(map) => Ok(map),
1118            Err(array) => Ok(Canonical::execute(array, ctx)?.into_map()),
1119        }
1120    }
1121}
1122
1123/// Execute the array to canonical form and unwrap as a [`FixedSizeListArray`].
1124///
1125/// This will panic if the array's dtype is not fixed size list.
1126impl Executable for FixedSizeListArray {
1127    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1128        match array.try_downcast::<FixedSizeList>() {
1129            Ok(fsl) => Ok(fsl),
1130            Err(array) => Ok(Canonical::execute(array, ctx)?.into_fixed_size_list()),
1131        }
1132    }
1133}
1134
1135/// Execute the array to canonical form and unwrap as a [`StructArray`].
1136///
1137/// This will panic if the array's dtype is not struct.
1138impl Executable for StructArray {
1139    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1140        match array.try_downcast::<Struct>() {
1141            Ok(struct_array) => Ok(struct_array),
1142            Err(array) => Ok(Canonical::execute(array, ctx)?.into_struct()),
1143        }
1144    }
1145}
1146
1147/// Execute the array to canonical form and unwrap as a [`UnionArray`].
1148///
1149/// This will panic if the array's dtype is not union.
1150impl Executable for UnionArray {
1151    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1152        match array.try_downcast::<Union>() {
1153            Ok(union_array) => Ok(union_array),
1154            Err(array) => Ok(Canonical::execute(array, ctx)?.into_union()),
1155        }
1156    }
1157}
1158
1159/// Execute the array to canonical form and unwrap as a [`VariantArray`].
1160///
1161/// This will panic if the array's dtype is not variant.
1162impl Executable for VariantArray {
1163    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1164        match array.try_downcast::<Variant>() {
1165            Ok(variant_array) => Ok(variant_array),
1166            Err(array) => match Canonical::execute(array, ctx)? {
1167                Canonical::Variant(variant_array) => Ok(variant_array),
1168                canonical => vortex_panic!("Cannot unwrap VariantArray from {:?}", canonical),
1169            },
1170        }
1171    }
1172}
1173
1174/// A view into a canonical array type.
1175///
1176/// Uses `ArrayView<V>` because these are obtained by
1177/// downcasting through the `Matcher` trait which returns `ArrayView<V>`.
1178#[derive(Debug, Clone, Copy)]
1179pub enum CanonicalView<'a> {
1180    Null(ArrayView<'a, Null>),
1181    Bool(ArrayView<'a, Bool>),
1182    Primitive(ArrayView<'a, Primitive>),
1183    Decimal(ArrayView<'a, Decimal>),
1184    VarBinView(ArrayView<'a, VarBinView>),
1185    List(ArrayView<'a, ListView>),
1186    Map(ArrayView<'a, Map>),
1187    FixedSizeList(ArrayView<'a, FixedSizeList>),
1188    Struct(ArrayView<'a, Struct>),
1189    Union(ArrayView<'a, Union>),
1190    Extension(ArrayView<'a, Extension>),
1191    Variant(ArrayView<'a, Variant>),
1192}
1193
1194impl From<CanonicalView<'_>> for Canonical {
1195    fn from(value: CanonicalView<'_>) -> Self {
1196        match value {
1197            CanonicalView::Null(a) => Canonical::Null(a.into_owned()),
1198            CanonicalView::Bool(a) => Canonical::Bool(a.into_owned()),
1199            CanonicalView::Primitive(a) => Canonical::Primitive(a.into_owned()),
1200            CanonicalView::Decimal(a) => Canonical::Decimal(a.into_owned()),
1201            CanonicalView::VarBinView(a) => Canonical::VarBinView(a.into_owned()),
1202            CanonicalView::List(a) => Canonical::List(a.into_owned()),
1203            CanonicalView::Map(a) => Canonical::Map(a.into_owned()),
1204            CanonicalView::FixedSizeList(a) => Canonical::FixedSizeList(a.into_owned()),
1205            CanonicalView::Struct(a) => Canonical::Struct(a.into_owned()),
1206            CanonicalView::Union(a) => Canonical::Union(a.into_owned()),
1207            CanonicalView::Extension(a) => Canonical::Extension(a.into_owned()),
1208            CanonicalView::Variant(a) => Canonical::Variant(a.into_owned()),
1209        }
1210    }
1211}
1212
1213impl CanonicalView<'_> {
1214    /// Convert to a type-erased [`ArrayRef`].
1215    pub fn to_array_ref(&self) -> ArrayRef {
1216        match self {
1217            CanonicalView::Null(a) => a.array().clone(),
1218            CanonicalView::Bool(a) => a.array().clone(),
1219            CanonicalView::Primitive(a) => a.array().clone(),
1220            CanonicalView::Decimal(a) => a.array().clone(),
1221            CanonicalView::VarBinView(a) => a.array().clone(),
1222            CanonicalView::List(a) => a.array().clone(),
1223            CanonicalView::Map(a) => a.array().clone(),
1224            CanonicalView::FixedSizeList(a) => a.array().clone(),
1225            CanonicalView::Struct(a) => a.array().clone(),
1226            CanonicalView::Union(a) => a.array().clone(),
1227            CanonicalView::Extension(a) => a.array().clone(),
1228            CanonicalView::Variant(a) => a.array().clone(),
1229        }
1230    }
1231}
1232
1233/// A matcher for any canonical array type.
1234pub struct AnyCanonical;
1235impl Matcher for AnyCanonical {
1236    type Match<'a> = CanonicalView<'a>;
1237
1238    #[inline]
1239    fn matches(array: &ArrayRef) -> bool {
1240        array.is::<Null>()
1241            || array.is::<Bool>()
1242            || array.is::<Primitive>()
1243            || array.is::<Decimal>()
1244            || array.is::<Struct>()
1245            || array.is::<Union>()
1246            || array.is::<ListView>()
1247            || array.is::<Map>()
1248            || array.is::<FixedSizeList>()
1249            || array.is::<VarBinView>()
1250            || array.is::<Variant>()
1251            || array.is::<Extension>()
1252    }
1253
1254    #[inline]
1255    fn try_match(array: &ArrayRef) -> Option<Self::Match<'_>> {
1256        if let Some(a) = array.as_opt::<Null>() {
1257            Some(CanonicalView::Null(a))
1258        } else if let Some(a) = array.as_opt::<Bool>() {
1259            Some(CanonicalView::Bool(a))
1260        } else if let Some(a) = array.as_opt::<Primitive>() {
1261            Some(CanonicalView::Primitive(a))
1262        } else if let Some(a) = array.as_opt::<Decimal>() {
1263            Some(CanonicalView::Decimal(a))
1264        } else if let Some(a) = array.as_opt::<Struct>() {
1265            Some(CanonicalView::Struct(a))
1266        } else if let Some(a) = array.as_opt::<Union>() {
1267            Some(CanonicalView::Union(a))
1268        } else if let Some(a) = array.as_opt::<ListView>() {
1269            Some(CanonicalView::List(a))
1270        } else if let Some(a) = array.as_opt::<Map>() {
1271            Some(CanonicalView::Map(a))
1272        } else if let Some(a) = array.as_opt::<FixedSizeList>() {
1273            Some(CanonicalView::FixedSizeList(a))
1274        } else if let Some(a) = array.as_opt::<VarBinView>() {
1275            Some(CanonicalView::VarBinView(a))
1276        } else if let Some(a) = array.as_opt::<Variant>() {
1277            Some(CanonicalView::Variant(a))
1278        } else {
1279            array.as_opt::<Extension>().map(CanonicalView::Extension)
1280        }
1281    }
1282}
1283
1284#[cfg(test)]
1285mod test {
1286    use std::sync::LazyLock;
1287
1288    use vortex_error::VortexResult;
1289    use vortex_error::vortex_err;
1290    use vortex_session::VortexSession;
1291
1292    use crate::ArrayRef;
1293    use crate::Canonical;
1294    use crate::CanonicalValidity;
1295    use crate::IntoArray;
1296    use crate::VortexSessionExecute;
1297    use crate::arrays::Constant;
1298    use crate::arrays::ConstantArray;
1299    use crate::arrays::Primitive;
1300    use crate::arrays::Struct;
1301    use crate::arrays::Variant;
1302    use crate::arrays::VariantArray;
1303    use crate::arrays::struct_::StructArrayExt;
1304    use crate::arrays::variant::VariantArraySlotsExt;
1305    use crate::canonical::StructArray;
1306    use crate::dtype::Nullability;
1307    use crate::scalar::Scalar;
1308
1309    /// A shared session for these canonical tests, used to create execution contexts.
1310    static SESSION: LazyLock<VortexSession> = LazyLock::new(crate::array_session);
1311
1312    fn variant_core_storage(len: usize) -> ArrayRef {
1313        ConstantArray::new(
1314            Scalar::variant(Scalar::primitive(1i32, Nullability::NonNullable)),
1315            len,
1316        )
1317        .into_array()
1318    }
1319
1320    #[test]
1321    fn canonical_validity_canonicalizes_variant_shredded_physical_slots() -> VortexResult<()> {
1322        let len = 2;
1323        let nested_shredded =
1324            StructArray::try_from_iter([("value", ConstantArray::new(10i32, len).into_array())])?;
1325        let inner_variant = VariantArray::try_new(
1326            variant_core_storage(len),
1327            Some(nested_shredded.into_array()),
1328        )?;
1329        let outer_variant =
1330            VariantArray::try_new(variant_core_storage(len), Some(inner_variant.into_array()))?;
1331
1332        let mut ctx = SESSION.create_execution_ctx();
1333        let Canonical::Variant(canonical) = outer_variant
1334            .into_array()
1335            .execute::<CanonicalValidity>(&mut ctx)?
1336            .0
1337        else {
1338            return Err(vortex_err!("expected canonical variant"));
1339        };
1340
1341        let nested_variant = canonical
1342            .shredded()
1343            .and_then(|shredded| shredded.as_opt::<Variant>())
1344            .ok_or_else(|| vortex_err!("expected nested variant shredded child"))?;
1345        let nested_struct = nested_variant
1346            .shredded()
1347            .and_then(|shredded| shredded.as_opt::<Struct>())
1348            .ok_or_else(|| vortex_err!("expected nested struct shredded child"))?;
1349        let value = nested_struct.unmasked_field_by_name("value")?;
1350
1351        assert!(value.is::<Primitive>());
1352        assert!(!value.is::<Constant>());
1353
1354        Ok(())
1355    }
1356}