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 is 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::VarBinView(array) => Ok(Canonical::VarBinView(array.compact_buffers(ctx)?)),
290            Canonical::List(array) => Ok(Canonical::List(
291                array.rebuild(ListViewRebuildMode::TrimElements, ctx)?,
292            )),
293            Canonical::Map(array) => Ok(Canonical::Map(MapArray::new(
294                array.map_dtype().clone(),
295                array
296                    .entries()
297                    .as_::<ListView>()
298                    .into_owned()
299                    .rebuild(ListViewRebuildMode::TrimElements, ctx)?,
300            ))),
301            _ => Ok(self.clone()),
302        }
303    }
304}
305
306// Unwrap canonical type back down to specialized type.
307impl Canonical {
308    pub fn as_null(&self) -> &NullArray {
309        if let Canonical::Null(a) = self {
310            a
311        } else {
312            vortex_panic!("Cannot get NullArray from {:?}", &self)
313        }
314    }
315
316    pub fn into_null(self) -> NullArray {
317        if let Canonical::Null(a) = self {
318            a
319        } else {
320            vortex_panic!("Cannot unwrap NullArray from {:?}", &self)
321        }
322    }
323
324    pub fn as_bool(&self) -> &BoolArray {
325        if let Canonical::Bool(a) = self {
326            a
327        } else {
328            vortex_panic!("Cannot get BoolArray from {:?}", &self)
329        }
330    }
331
332    pub fn into_bool(self) -> BoolArray {
333        if let Canonical::Bool(a) = self {
334            a
335        } else {
336            vortex_panic!("Cannot unwrap BoolArray from {:?}", &self)
337        }
338    }
339
340    pub fn as_primitive(&self) -> &PrimitiveArray {
341        if let Canonical::Primitive(a) = self {
342            a
343        } else {
344            vortex_panic!("Cannot get PrimitiveArray from {:?}", &self)
345        }
346    }
347
348    pub fn into_primitive(self) -> PrimitiveArray {
349        if let Canonical::Primitive(a) = self {
350            a
351        } else {
352            vortex_panic!("Cannot unwrap PrimitiveArray from {:?}", &self)
353        }
354    }
355
356    pub fn as_decimal(&self) -> &DecimalArray {
357        if let Canonical::Decimal(a) = self {
358            a
359        } else {
360            vortex_panic!("Cannot get DecimalArray from {:?}", &self)
361        }
362    }
363
364    pub fn into_decimal(self) -> DecimalArray {
365        if let Canonical::Decimal(a) = self {
366            a
367        } else {
368            vortex_panic!("Cannot unwrap DecimalArray from {:?}", &self)
369        }
370    }
371
372    pub fn as_varbinview(&self) -> &VarBinViewArray {
373        if let Canonical::VarBinView(a) = self {
374            a
375        } else {
376            vortex_panic!("Cannot get VarBinViewArray from {:?}", &self)
377        }
378    }
379
380    pub fn into_varbinview(self) -> VarBinViewArray {
381        if let Canonical::VarBinView(a) = self {
382            a
383        } else {
384            vortex_panic!("Cannot unwrap VarBinViewArray from {:?}", &self)
385        }
386    }
387
388    pub fn as_listview(&self) -> &ListViewArray {
389        if let Canonical::List(a) = self {
390            a
391        } else {
392            vortex_panic!("Cannot get ListArray from {:?}", &self)
393        }
394    }
395
396    pub fn into_listview(self) -> ListViewArray {
397        if let Canonical::List(a) = self {
398            a
399        } else {
400            vortex_panic!("Cannot unwrap ListArray from {:?}", &self)
401        }
402    }
403
404    pub fn as_map(&self) -> &MapArray {
405        if let Canonical::Map(a) = self {
406            a
407        } else {
408            vortex_panic!("Cannot get MapArray from {:?}", &self)
409        }
410    }
411
412    pub fn into_map(self) -> MapArray {
413        if let Canonical::Map(a) = self {
414            a
415        } else {
416            vortex_panic!("Cannot unwrap MapArray from {:?}", &self)
417        }
418    }
419
420    pub fn as_fixed_size_list(&self) -> &FixedSizeListArray {
421        if let Canonical::FixedSizeList(a) = self {
422            a
423        } else {
424            vortex_panic!("Cannot get FixedSizeListArray from {:?}", &self)
425        }
426    }
427
428    pub fn into_fixed_size_list(self) -> FixedSizeListArray {
429        if let Canonical::FixedSizeList(a) = self {
430            a
431        } else {
432            vortex_panic!("Cannot unwrap FixedSizeListArray from {:?}", &self)
433        }
434    }
435
436    pub fn as_struct(&self) -> &StructArray {
437        if let Canonical::Struct(a) = self {
438            a
439        } else {
440            vortex_panic!("Cannot get StructArray from {:?}", &self)
441        }
442    }
443
444    pub fn into_struct(self) -> StructArray {
445        if let Canonical::Struct(a) = self {
446            a
447        } else {
448            vortex_panic!("Cannot unwrap StructArray from {:?}", &self)
449        }
450    }
451
452    /// Return this canonical array as a sparse [`UnionArray`].
453    pub fn as_union(&self) -> &UnionArray {
454        if let Canonical::Union(a) = self {
455            a
456        } else {
457            vortex_panic!("Cannot get UnionArray from {:?}", &self)
458        }
459    }
460
461    /// Unwrap this canonical array as a sparse [`UnionArray`].
462    pub fn into_union(self) -> UnionArray {
463        if let Canonical::Union(a) = self {
464            a
465        } else {
466            vortex_panic!("Cannot unwrap UnionArray from {:?}", &self)
467        }
468    }
469
470    pub fn as_extension(&self) -> &ExtensionArray {
471        if let Canonical::Extension(a) = self {
472            a
473        } else {
474            vortex_panic!("Cannot get ExtensionArray from {:?}", &self)
475        }
476    }
477
478    pub fn into_extension(self) -> ExtensionArray {
479        if let Canonical::Extension(a) = self {
480            a
481        } else {
482            vortex_panic!("Cannot unwrap ExtensionArray from {:?}", &self)
483        }
484    }
485}
486
487impl IntoArray for Canonical {
488    fn into_array(self) -> ArrayRef {
489        match_each_canonical!(self, |arr| arr.into_array())
490    }
491}
492
493/// Trait for types that can be converted from an owned type into an owned array variant.
494///
495/// # Canonicalization
496///
497/// This trait has a blanket implementation for all types implementing [ToCanonical].
498#[deprecated(note = "use `array.execute::<T>(ctx)` instead")]
499pub trait ToCanonical {
500    /// Canonicalize into a [`NullArray`] if the target is [`Null`](DType::Null) typed.
501    #[deprecated(note = "use `array.execute::<NullArray>(ctx)` instead")]
502    fn to_null(&self) -> NullArray;
503
504    /// Canonicalize into a [`BoolArray`] if the target is [`Bool`](DType::Bool) typed.
505    #[deprecated(note = "use `array.execute::<BoolArray>(ctx)` instead")]
506    fn to_bool(&self) -> BoolArray;
507
508    /// Canonicalize into a [`PrimitiveArray`] if the target is [`Primitive`](DType::Primitive)
509    /// typed.
510    #[deprecated(note = "use `array.execute::<PrimitiveArray>(ctx)` instead")]
511    fn to_primitive(&self) -> PrimitiveArray;
512
513    /// Canonicalize into a [`DecimalArray`] if the target is [`Decimal`](DType::Decimal)
514    /// typed.
515    #[deprecated(note = "use `array.execute::<DecimalArray>(ctx)` instead")]
516    fn to_decimal(&self) -> DecimalArray;
517
518    /// Canonicalize into a [`StructArray`] if the target is [`Struct`](DType::Struct) typed.
519    #[deprecated(note = "use `array.execute::<StructArray>(ctx)` instead")]
520    fn to_struct(&self) -> StructArray;
521
522    /// Canonicalize into a [`ListViewArray`] if the target is [`List`](DType::List) typed.
523    #[deprecated(note = "use `array.execute::<ListViewArray>(ctx)` instead")]
524    fn to_listview(&self) -> ListViewArray;
525
526    /// Canonicalize into a [`MapArray`] if the target is [`Map`](DType::Map) typed.
527    #[deprecated(note = "use `array.execute::<MapArray>(ctx)` instead")]
528    fn to_map(&self) -> MapArray;
529
530    /// Canonicalize into a [`FixedSizeListArray`] if the target is [`List`](DType::FixedSizeList)
531    /// typed.
532    #[deprecated(note = "use `array.execute::<FixedSizeListArray>(ctx)` instead")]
533    fn to_fixed_size_list(&self) -> FixedSizeListArray;
534
535    /// Canonicalize into a [`VarBinViewArray`] if the target is [`Utf8`](DType::Utf8)
536    /// or [`Binary`](DType::Binary) typed.
537    #[deprecated(note = "use `array.execute::<VarBinViewArray>(ctx)` instead")]
538    fn to_varbinview(&self) -> VarBinViewArray;
539
540    /// Canonicalize into an [`ExtensionArray`] if the array is [`Extension`](DType::Extension)
541    /// typed.
542    #[deprecated(note = "use `array.execute::<ExtensionArray>(ctx)` instead")]
543    fn to_extension(&self) -> ExtensionArray;
544}
545
546// Blanket impl for all Array encodings.
547#[expect(deprecated)]
548impl ToCanonical for ArrayRef {
549    fn to_null(&self) -> NullArray {
550        #[expect(deprecated)]
551        let result = self.to_canonical().vortex_expect("to_canonical failed");
552        result.into_null()
553    }
554
555    fn to_bool(&self) -> BoolArray {
556        #[expect(deprecated)]
557        let result = self.to_canonical().vortex_expect("to_canonical failed");
558        result.into_bool()
559    }
560
561    fn to_primitive(&self) -> PrimitiveArray {
562        #[expect(deprecated)]
563        let result = self.to_canonical().vortex_expect("to_canonical failed");
564        result.into_primitive()
565    }
566
567    fn to_decimal(&self) -> DecimalArray {
568        #[expect(deprecated)]
569        let result = self.to_canonical().vortex_expect("to_canonical failed");
570        result.into_decimal()
571    }
572
573    fn to_struct(&self) -> StructArray {
574        #[expect(deprecated)]
575        let result = self.to_canonical().vortex_expect("to_canonical failed");
576        result.into_struct()
577    }
578
579    fn to_listview(&self) -> ListViewArray {
580        #[expect(deprecated)]
581        let result = self.to_canonical().vortex_expect("to_canonical failed");
582        result.into_listview()
583    }
584
585    fn to_map(&self) -> MapArray {
586        #[expect(deprecated)]
587        let result = self.to_canonical().vortex_expect("to_canonical failed");
588        result.into_map()
589    }
590
591    fn to_fixed_size_list(&self) -> FixedSizeListArray {
592        #[expect(deprecated)]
593        let result = self.to_canonical().vortex_expect("to_canonical failed");
594        result.into_fixed_size_list()
595    }
596
597    fn to_varbinview(&self) -> VarBinViewArray {
598        #[expect(deprecated)]
599        let result = self.to_canonical().vortex_expect("to_canonical failed");
600        result.into_varbinview()
601    }
602
603    fn to_extension(&self) -> ExtensionArray {
604        #[expect(deprecated)]
605        let result = self.to_canonical().vortex_expect("to_canonical failed");
606        result.into_extension()
607    }
608}
609
610impl From<Canonical> for ArrayRef {
611    fn from(value: Canonical) -> Self {
612        match_each_canonical!(value, |arr| arr.into_array())
613    }
614}
615
616/// Execute into [`Canonical`] by running `execute_until` with the [`AnyCanonical`] matcher.
617///
618/// Unlike executing into [`crate::Columnar`], this will fully expand constant arrays into their
619/// canonical form. Callers should prefer to execute into `Columnar` if they are able to optimize
620/// their use for constant arrays.
621impl Executable for Canonical {
622    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
623        let result = array.execute_until::<AnyCanonical>(ctx)?;
624        Ok(result
625            .as_opt::<AnyCanonical>()
626            .map(Canonical::from)
627            .vortex_expect("execute_until::<AnyCanonical> must return a canonical array"))
628    }
629}
630
631/// Recursively execute the array until it reaches canonical form along with its validity.
632///
633/// Callers should prefer to execute into `Columnar` instead of this specific target.
634/// This target is useful when preparing arrays for writing.
635pub struct CanonicalValidity(pub Canonical);
636
637impl Executable for CanonicalValidity {
638    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
639        match array.execute::<Canonical>(ctx)? {
640            n @ Canonical::Null(_) => Ok(CanonicalValidity(n)),
641            Canonical::Bool(b) => {
642                let validity = child_to_validity(b.slots()[0].as_ref(), b.dtype().nullability());
643                let len = b.len();
644                let BoolDataParts { bits, meta } = b.into_data().into_parts(len);
645                Ok(CanonicalValidity(Canonical::Bool(
646                    BoolArray::try_new_from_handle(
647                        bits,
648                        meta.offset(),
649                        meta.len(),
650                        validity.execute(ctx)?,
651                    )?,
652                )))
653            }
654            Canonical::Primitive(p) => {
655                let PrimitiveDataParts {
656                    ptype,
657                    buffer,
658                    validity,
659                } = p.into_data_parts();
660                Ok(CanonicalValidity(Canonical::Primitive(unsafe {
661                    PrimitiveArray::new_unchecked_from_handle(buffer, ptype, validity.execute(ctx)?)
662                })))
663            }
664            Canonical::Decimal(d) => {
665                let DecimalDataParts {
666                    decimal_dtype,
667                    values,
668                    values_type,
669                    validity,
670                } = d.into_data_parts();
671                Ok(CanonicalValidity(Canonical::Decimal(unsafe {
672                    DecimalArray::new_unchecked_handle(
673                        values,
674                        values_type,
675                        decimal_dtype,
676                        validity.execute(ctx)?,
677                    )
678                })))
679            }
680            Canonical::VarBinView(vbv) => {
681                let VarBinViewDataParts {
682                    dtype,
683                    buffers,
684                    views,
685                    validity,
686                } = vbv.into_data_parts();
687                Ok(CanonicalValidity(Canonical::VarBinView(unsafe {
688                    VarBinViewArray::new_handle_unchecked(
689                        views,
690                        buffers,
691                        dtype,
692                        validity.execute(ctx)?,
693                    )
694                })))
695            }
696            Canonical::List(l) => {
697                let zctl = l.is_zero_copy_to_list();
698                let ListViewDataParts {
699                    elements,
700                    offsets,
701                    sizes,
702                    validity,
703                    ..
704                } = l.into_data_parts();
705                Ok(CanonicalValidity(Canonical::List(unsafe {
706                    ListViewArray::new_unchecked(elements, offsets, sizes, validity.execute(ctx)?)
707                        .with_zero_copy_to_list(zctl)
708                })))
709            }
710            Canonical::Map(map) => {
711                let map_dtype = map.map_dtype().clone();
712                let entries = map.entries().clone();
713                Ok(CanonicalValidity(Canonical::Map(MapArray::new(
714                    map_dtype,
715                    entries.execute::<CanonicalValidity>(ctx)?.0.into_listview(),
716                ))))
717            }
718            Canonical::FixedSizeList(fsl) => {
719                let list_size = fsl.list_size();
720                let len = fsl.len();
721                let parts = fsl.into_data_parts();
722                let elements = parts.elements;
723                let validity = parts.validity;
724                Ok(CanonicalValidity(Canonical::FixedSizeList(
725                    FixedSizeListArray::new(elements, list_size, validity.execute(ctx)?, len),
726                )))
727            }
728            Canonical::Struct(st) => {
729                let len = st.len();
730                let StructDataParts {
731                    struct_fields,
732                    fields,
733                    validity,
734                } = st.into_data_parts();
735                Ok(CanonicalValidity(Canonical::Struct(unsafe {
736                    StructArray::new_unchecked(fields, struct_fields, len, validity.execute(ctx)?)
737                })))
738            }
739            Canonical::Union(union) => {
740                let UnionDataParts {
741                    variants,
742                    type_ids,
743                    children,
744                } = union.into_data_parts();
745                let type_ids = type_ids.execute::<CanonicalValidity>(ctx)?.0.into_array();
746
747                Ok(CanonicalValidity(Canonical::Union(unsafe {
748                    UnionArray::new_unchecked(type_ids, variants, children.iter().cloned())
749                })))
750            }
751            Canonical::Extension(ext) => Ok(CanonicalValidity(Canonical::Extension(
752                ExtensionArray::new(
753                    ext.ext_dtype().clone(),
754                    ext.storage_array()
755                        .clone()
756                        .execute::<CanonicalValidity>(ctx)?
757                        .0
758                        .into_array(),
759                ),
760            ))),
761            Canonical::Variant(variant) => {
762                let core_storage = recursively_canonicalize_slots(variant.core_storage(), ctx)?;
763                let shredded = variant
764                    .shredded()
765                    .map(|shredded| {
766                        if shredded.is::<Variant>() {
767                            recursively_canonicalize_slots(shredded, ctx)
768                        } else {
769                            shredded
770                                .clone()
771                                .execute::<CanonicalValidity>(ctx)
772                                .map(|canonical| canonical.0.into_array())
773                        }
774                    })
775                    .transpose()?;
776                Ok(CanonicalValidity(Canonical::Variant(
777                    VariantArray::try_new(core_storage, shredded)?,
778                )))
779            }
780        }
781    }
782}
783
784/// Recursively execute the array until all of its children are canonical.
785///
786/// This method is useful to guarantee that all operators are fully executed,
787/// callers should prefer an execution target that's suitable for their use case instead of this one.
788pub struct RecursiveCanonical(pub Canonical);
789
790// TODO: Currently only used for Variant, in the future
791// can probably be used for more canonical types like Struct.
792fn recursively_canonicalize_slots(
793    array: &ArrayRef,
794    ctx: &mut ExecutionCtx,
795) -> VortexResult<ArrayRef> {
796    let slots = array
797        .slots()
798        .iter()
799        .map(|slot| {
800            slot.as_ref()
801                .map(|child| {
802                    child
803                        .clone()
804                        .execute::<RecursiveCanonical>(ctx)
805                        .map(|canonical| canonical.0.into_array())
806                })
807                .transpose()
808        })
809        .collect::<VortexResult<ArraySlots>>()?;
810    // SAFETY: recursive canonicalization rewrites child slots to equivalent canonical
811    // representations, preserving the parent array's logical values and statistics.
812    unsafe { array.clone().with_slots(slots) }
813}
814impl Executable for RecursiveCanonical {
815    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
816        match array.execute::<Canonical>(ctx)? {
817            n @ Canonical::Null(_) => Ok(RecursiveCanonical(n)),
818            Canonical::Bool(b) => {
819                let validity = child_to_validity(b.slots()[0].as_ref(), b.dtype().nullability());
820                let len = b.len();
821                let BoolDataParts { bits, meta } = b.into_data().into_parts(len);
822                Ok(RecursiveCanonical(Canonical::Bool(
823                    BoolArray::try_new_from_handle(
824                        bits,
825                        meta.offset(),
826                        meta.len(),
827                        validity.execute(ctx)?,
828                    )?,
829                )))
830            }
831            Canonical::Primitive(p) => {
832                let PrimitiveDataParts {
833                    ptype,
834                    buffer,
835                    validity,
836                } = p.into_data_parts();
837                Ok(RecursiveCanonical(Canonical::Primitive(unsafe {
838                    PrimitiveArray::new_unchecked_from_handle(buffer, ptype, validity.execute(ctx)?)
839                })))
840            }
841            Canonical::Decimal(d) => {
842                let DecimalDataParts {
843                    decimal_dtype,
844                    values,
845                    values_type,
846                    validity,
847                } = d.into_data_parts();
848                Ok(RecursiveCanonical(Canonical::Decimal(unsafe {
849                    DecimalArray::new_unchecked_handle(
850                        values,
851                        values_type,
852                        decimal_dtype,
853                        validity.execute(ctx)?,
854                    )
855                })))
856            }
857            Canonical::VarBinView(vbv) => {
858                let VarBinViewDataParts {
859                    dtype,
860                    buffers,
861                    views,
862                    validity,
863                } = vbv.into_data_parts();
864                Ok(RecursiveCanonical(Canonical::VarBinView(unsafe {
865                    VarBinViewArray::new_handle_unchecked(
866                        views,
867                        buffers,
868                        dtype,
869                        validity.execute(ctx)?,
870                    )
871                })))
872            }
873            Canonical::List(l) => {
874                let zctl = l.is_zero_copy_to_list();
875                let ListViewDataParts {
876                    elements,
877                    offsets,
878                    sizes,
879                    validity,
880                    ..
881                } = l.into_data_parts();
882                Ok(RecursiveCanonical(Canonical::List(unsafe {
883                    ListViewArray::new_unchecked(
884                        elements.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
885                        offsets.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
886                        sizes.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
887                        validity.execute(ctx)?,
888                    )
889                    .with_zero_copy_to_list(zctl)
890                })))
891            }
892            Canonical::Map(map) => {
893                let map_dtype = map.map_dtype().clone();
894                let entries = map.entries().clone();
895                Ok(RecursiveCanonical(Canonical::Map(MapArray::new(
896                    map_dtype,
897                    entries
898                        .execute::<RecursiveCanonical>(ctx)?
899                        .0
900                        .into_listview(),
901                ))))
902            }
903            Canonical::FixedSizeList(fsl) => {
904                let list_size = fsl.list_size();
905                let len = fsl.len();
906                let parts = fsl.into_data_parts();
907                let elements = parts.elements;
908                let validity = parts.validity;
909                Ok(RecursiveCanonical(Canonical::FixedSizeList(
910                    FixedSizeListArray::new(
911                        elements.execute::<RecursiveCanonical>(ctx)?.0.into_array(),
912                        list_size,
913                        validity.execute(ctx)?,
914                        len,
915                    ),
916                )))
917            }
918            Canonical::Struct(st) => {
919                let len = st.len();
920                let StructDataParts {
921                    struct_fields,
922                    fields,
923                    validity,
924                } = st.into_data_parts();
925                let executed_fields = fields
926                    .into_iter()
927                    .map(|f| Ok(f.execute::<RecursiveCanonical>(ctx)?.0.into_array()))
928                    .collect::<VortexResult<Vec<_>>>()?;
929
930                Ok(RecursiveCanonical(Canonical::Struct(unsafe {
931                    StructArray::new_unchecked(
932                        executed_fields,
933                        struct_fields,
934                        len,
935                        validity.execute(ctx)?,
936                    )
937                })))
938            }
939            Canonical::Union(union) => {
940                let UnionDataParts {
941                    variants,
942                    type_ids,
943                    children,
944                } = union.into_data_parts();
945                let type_ids = type_ids.execute::<RecursiveCanonical>(ctx)?.0.into_array();
946                let children = children
947                    .iter()
948                    .cloned()
949                    .map(|child| {
950                        child
951                            .execute::<RecursiveCanonical>(ctx)
952                            .map(|canonical| canonical.0.into_array())
953                    })
954                    .collect::<VortexResult<Vec<_>>>()?;
955
956                Ok(RecursiveCanonical(Canonical::Union(unsafe {
957                    UnionArray::new_unchecked(type_ids, variants, children)
958                })))
959            }
960            Canonical::Extension(ext) => Ok(RecursiveCanonical(Canonical::Extension(
961                ExtensionArray::new(
962                    ext.ext_dtype().clone(),
963                    ext.storage_array()
964                        .clone()
965                        .execute::<RecursiveCanonical>(ctx)?
966                        .0
967                        .into_array(),
968                ),
969            ))),
970            Canonical::Variant(variant) => {
971                let core_storage = recursively_canonicalize_slots(variant.core_storage(), ctx)?;
972                let shredded = variant
973                    .shredded()
974                    .map(|shredded| {
975                        if shredded.is::<Variant>() {
976                            recursively_canonicalize_slots(shredded, ctx)
977                        } else {
978                            shredded
979                                .clone()
980                                .execute::<RecursiveCanonical>(ctx)
981                                .map(|canonical| canonical.0.into_array())
982                        }
983                    })
984                    .transpose()?;
985                Ok(RecursiveCanonical(Canonical::Variant(
986                    VariantArray::try_new(core_storage, shredded)?,
987                )))
988            }
989        }
990    }
991}
992
993/// Execute a primitive typed array into a buffer of native values, assuming all values are valid.
994///
995/// # Errors
996///
997/// Returns a `VortexError` if the array is not all-valid (has any nulls).
998impl<T: NativePType> Executable for Buffer<T> {
999    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1000        let array = PrimitiveArray::execute(array, ctx)?;
1001        vortex_ensure!(
1002            matches!(
1003                array.validity()?,
1004                Validity::NonNullable | Validity::AllValid
1005            ),
1006            "Cannot execute to native buffer: array is not all-valid."
1007        );
1008        Ok(array.into_buffer())
1009    }
1010}
1011
1012/// Execute the array to canonical form and unwrap as a [`PrimitiveArray`].
1013///
1014/// This will panic if the array's dtype is not primitive.
1015impl Executable for PrimitiveArray {
1016    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1017        match array.try_downcast::<Primitive>() {
1018            Ok(primitive) => Ok(primitive),
1019            Err(array) => Ok(Canonical::execute(array, ctx)?.into_primitive()),
1020        }
1021    }
1022}
1023
1024/// Execute the array to canonical form and unwrap as a [`BoolArray`].
1025///
1026/// This will panic if the array's dtype is not bool.
1027impl Executable for BoolArray {
1028    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1029        match array.try_downcast::<Bool>() {
1030            Ok(bool_array) => Ok(bool_array),
1031            Err(array) => Ok(Canonical::execute(array, ctx)?.into_bool()),
1032        }
1033    }
1034}
1035
1036/// Execute the array to a [`BitBuffer`], aka a non-nullable  [`BoolArray`].
1037///
1038/// This will panic if the array's dtype is not non-nullable bool.
1039impl Executable for BitBuffer {
1040    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1041        let bool = BoolArray::execute(array, ctx)?;
1042        assert!(
1043            !bool.dtype().is_nullable(),
1044            "bit buffer execute only works with non-nullable bool arrays"
1045        );
1046        Ok(bool.into_bit_buffer())
1047    }
1048}
1049
1050/// Execute the array to canonical form and unwrap as a [`NullArray`].
1051///
1052/// This will panic if the array's dtype is not null.
1053impl Executable for NullArray {
1054    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1055        match array.try_downcast::<Null>() {
1056            Ok(null_array) => Ok(null_array),
1057            Err(array) => Ok(Canonical::execute(array, ctx)?.into_null()),
1058        }
1059    }
1060}
1061
1062/// Execute the array to canonical form and unwrap as a [`VarBinViewArray`].
1063///
1064/// This will panic if the array's dtype is not utf8 or binary.
1065impl Executable for VarBinViewArray {
1066    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1067        match array.try_downcast::<VarBinView>() {
1068            Ok(varbinview) => Ok(varbinview),
1069            Err(array) => Ok(Canonical::execute(array, ctx)?.into_varbinview()),
1070        }
1071    }
1072}
1073
1074/// Execute the array to canonical form and unwrap as an [`ExtensionArray`].
1075///
1076/// This will panic if the array's dtype is not an extension type.
1077impl Executable for ExtensionArray {
1078    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1079        match array.try_downcast::<Extension>() {
1080            Ok(ext_array) => Ok(ext_array),
1081            Err(array) => Ok(Canonical::execute(array, ctx)?.into_extension()),
1082        }
1083    }
1084}
1085
1086/// Execute the array to canonical form and unwrap as a [`DecimalArray`].
1087///
1088/// This will panic if the array's dtype is not decimal.
1089impl Executable for DecimalArray {
1090    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1091        match array.try_downcast::<Decimal>() {
1092            Ok(decimal) => Ok(decimal),
1093            Err(array) => Ok(Canonical::execute(array, ctx)?.into_decimal()),
1094        }
1095    }
1096}
1097
1098/// Execute the array to canonical form and unwrap as a [`ListViewArray`].
1099///
1100/// This will panic if the array's dtype is not list.
1101impl Executable for ListViewArray {
1102    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1103        match array.try_downcast::<ListView>() {
1104            Ok(list) => Ok(list),
1105            Err(array) => Ok(Canonical::execute(array, ctx)?.into_listview()),
1106        }
1107    }
1108}
1109
1110/// Execute the array to canonical form and unwrap as a [`MapArray`].
1111///
1112/// This will panic if the array's dtype is not map.
1113impl Executable for MapArray {
1114    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1115        match array.try_downcast::<Map>() {
1116            Ok(map) => Ok(map),
1117            Err(array) => Ok(Canonical::execute(array, ctx)?.into_map()),
1118        }
1119    }
1120}
1121
1122/// Execute the array to canonical form and unwrap as a [`FixedSizeListArray`].
1123///
1124/// This will panic if the array's dtype is not fixed size list.
1125impl Executable for FixedSizeListArray {
1126    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1127        match array.try_downcast::<FixedSizeList>() {
1128            Ok(fsl) => Ok(fsl),
1129            Err(array) => Ok(Canonical::execute(array, ctx)?.into_fixed_size_list()),
1130        }
1131    }
1132}
1133
1134/// Execute the array to canonical form and unwrap as a [`StructArray`].
1135///
1136/// This will panic if the array's dtype is not struct.
1137impl Executable for StructArray {
1138    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1139        match array.try_downcast::<Struct>() {
1140            Ok(struct_array) => Ok(struct_array),
1141            Err(array) => Ok(Canonical::execute(array, ctx)?.into_struct()),
1142        }
1143    }
1144}
1145
1146/// Execute the array to canonical form and unwrap as a [`UnionArray`].
1147///
1148/// This will panic if the array's dtype is not union.
1149impl Executable for UnionArray {
1150    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1151        match array.try_downcast::<Union>() {
1152            Ok(union_array) => Ok(union_array),
1153            Err(array) => Ok(Canonical::execute(array, ctx)?.into_union()),
1154        }
1155    }
1156}
1157
1158/// Execute the array to canonical form and unwrap as a [`VariantArray`].
1159///
1160/// This will panic if the array's dtype is not variant.
1161impl Executable for VariantArray {
1162    fn execute(array: ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Self> {
1163        match array.try_downcast::<Variant>() {
1164            Ok(variant_array) => Ok(variant_array),
1165            Err(array) => match Canonical::execute(array, ctx)? {
1166                Canonical::Variant(variant_array) => Ok(variant_array),
1167                canonical => vortex_panic!("Cannot unwrap VariantArray from {:?}", canonical),
1168            },
1169        }
1170    }
1171}
1172
1173/// A view into a canonical array type.
1174///
1175/// Uses `ArrayView<V>` because these are obtained by
1176/// downcasting through the `Matcher` trait which returns `ArrayView<V>`.
1177#[derive(Debug, Clone, Copy)]
1178pub enum CanonicalView<'a> {
1179    Null(ArrayView<'a, Null>),
1180    Bool(ArrayView<'a, Bool>),
1181    Primitive(ArrayView<'a, Primitive>),
1182    Decimal(ArrayView<'a, Decimal>),
1183    VarBinView(ArrayView<'a, VarBinView>),
1184    List(ArrayView<'a, ListView>),
1185    Map(ArrayView<'a, Map>),
1186    FixedSizeList(ArrayView<'a, FixedSizeList>),
1187    Struct(ArrayView<'a, Struct>),
1188    Union(ArrayView<'a, Union>),
1189    Extension(ArrayView<'a, Extension>),
1190    Variant(ArrayView<'a, Variant>),
1191}
1192
1193impl From<CanonicalView<'_>> for Canonical {
1194    fn from(value: CanonicalView<'_>) -> Self {
1195        match value {
1196            CanonicalView::Null(a) => Canonical::Null(a.into_owned()),
1197            CanonicalView::Bool(a) => Canonical::Bool(a.into_owned()),
1198            CanonicalView::Primitive(a) => Canonical::Primitive(a.into_owned()),
1199            CanonicalView::Decimal(a) => Canonical::Decimal(a.into_owned()),
1200            CanonicalView::VarBinView(a) => Canonical::VarBinView(a.into_owned()),
1201            CanonicalView::List(a) => Canonical::List(a.into_owned()),
1202            CanonicalView::Map(a) => Canonical::Map(a.into_owned()),
1203            CanonicalView::FixedSizeList(a) => Canonical::FixedSizeList(a.into_owned()),
1204            CanonicalView::Struct(a) => Canonical::Struct(a.into_owned()),
1205            CanonicalView::Union(a) => Canonical::Union(a.into_owned()),
1206            CanonicalView::Extension(a) => Canonical::Extension(a.into_owned()),
1207            CanonicalView::Variant(a) => Canonical::Variant(a.into_owned()),
1208        }
1209    }
1210}
1211
1212impl CanonicalView<'_> {
1213    /// Convert to a type-erased [`ArrayRef`].
1214    pub fn to_array_ref(&self) -> ArrayRef {
1215        match self {
1216            CanonicalView::Null(a) => a.array().clone(),
1217            CanonicalView::Bool(a) => a.array().clone(),
1218            CanonicalView::Primitive(a) => a.array().clone(),
1219            CanonicalView::Decimal(a) => a.array().clone(),
1220            CanonicalView::VarBinView(a) => a.array().clone(),
1221            CanonicalView::List(a) => a.array().clone(),
1222            CanonicalView::Map(a) => a.array().clone(),
1223            CanonicalView::FixedSizeList(a) => a.array().clone(),
1224            CanonicalView::Struct(a) => a.array().clone(),
1225            CanonicalView::Union(a) => a.array().clone(),
1226            CanonicalView::Extension(a) => a.array().clone(),
1227            CanonicalView::Variant(a) => a.array().clone(),
1228        }
1229    }
1230}
1231
1232/// A matcher for any canonical array type.
1233pub struct AnyCanonical;
1234impl Matcher for AnyCanonical {
1235    type Match<'a> = CanonicalView<'a>;
1236
1237    #[inline]
1238    fn matches(array: &ArrayRef) -> bool {
1239        array.is::<Null>()
1240            || array.is::<Bool>()
1241            || array.is::<Primitive>()
1242            || array.is::<Decimal>()
1243            || array.is::<Struct>()
1244            || array.is::<Union>()
1245            || array.is::<ListView>()
1246            || array.is::<Map>()
1247            || array.is::<FixedSizeList>()
1248            || array.is::<VarBinView>()
1249            || array.is::<Variant>()
1250            || array.is::<Extension>()
1251    }
1252
1253    #[inline]
1254    fn try_match(array: &ArrayRef) -> Option<Self::Match<'_>> {
1255        if let Some(a) = array.as_opt::<Null>() {
1256            Some(CanonicalView::Null(a))
1257        } else if let Some(a) = array.as_opt::<Bool>() {
1258            Some(CanonicalView::Bool(a))
1259        } else if let Some(a) = array.as_opt::<Primitive>() {
1260            Some(CanonicalView::Primitive(a))
1261        } else if let Some(a) = array.as_opt::<Decimal>() {
1262            Some(CanonicalView::Decimal(a))
1263        } else if let Some(a) = array.as_opt::<Struct>() {
1264            Some(CanonicalView::Struct(a))
1265        } else if let Some(a) = array.as_opt::<Union>() {
1266            Some(CanonicalView::Union(a))
1267        } else if let Some(a) = array.as_opt::<ListView>() {
1268            Some(CanonicalView::List(a))
1269        } else if let Some(a) = array.as_opt::<Map>() {
1270            Some(CanonicalView::Map(a))
1271        } else if let Some(a) = array.as_opt::<FixedSizeList>() {
1272            Some(CanonicalView::FixedSizeList(a))
1273        } else if let Some(a) = array.as_opt::<VarBinView>() {
1274            Some(CanonicalView::VarBinView(a))
1275        } else if let Some(a) = array.as_opt::<Variant>() {
1276            Some(CanonicalView::Variant(a))
1277        } else {
1278            array.as_opt::<Extension>().map(CanonicalView::Extension)
1279        }
1280    }
1281}
1282
1283#[cfg(test)]
1284mod test {
1285    use std::sync::LazyLock;
1286
1287    use vortex_error::VortexResult;
1288    use vortex_error::vortex_err;
1289    use vortex_session::VortexSession;
1290
1291    use crate::ArrayRef;
1292    use crate::Canonical;
1293    use crate::CanonicalValidity;
1294    use crate::IntoArray;
1295    use crate::VortexSessionExecute;
1296    use crate::arrays::Constant;
1297    use crate::arrays::ConstantArray;
1298    use crate::arrays::Primitive;
1299    use crate::arrays::Struct;
1300    use crate::arrays::Variant;
1301    use crate::arrays::VariantArray;
1302    use crate::arrays::struct_::StructArrayExt;
1303    use crate::arrays::variant::VariantArraySlotsExt;
1304    use crate::canonical::StructArray;
1305    use crate::dtype::Nullability;
1306    use crate::scalar::Scalar;
1307
1308    /// A shared session for these canonical tests, used to create execution contexts.
1309    static SESSION: LazyLock<VortexSession> = LazyLock::new(crate::array_session);
1310
1311    fn variant_core_storage(len: usize) -> ArrayRef {
1312        ConstantArray::new(
1313            Scalar::variant(Scalar::primitive(1i32, Nullability::NonNullable)),
1314            len,
1315        )
1316        .into_array()
1317    }
1318
1319    #[test]
1320    fn canonical_validity_canonicalizes_variant_shredded_physical_slots() -> VortexResult<()> {
1321        let len = 2;
1322        let nested_shredded =
1323            StructArray::try_from_iter([("value", ConstantArray::new(10i32, len).into_array())])?;
1324        let inner_variant = VariantArray::try_new(
1325            variant_core_storage(len),
1326            Some(nested_shredded.into_array()),
1327        )?;
1328        let outer_variant =
1329            VariantArray::try_new(variant_core_storage(len), Some(inner_variant.into_array()))?;
1330
1331        let mut ctx = SESSION.create_execution_ctx();
1332        let Canonical::Variant(canonical) = outer_variant
1333            .into_array()
1334            .execute::<CanonicalValidity>(&mut ctx)?
1335            .0
1336        else {
1337            return Err(vortex_err!("expected canonical variant"));
1338        };
1339
1340        let nested_variant = canonical
1341            .shredded()
1342            .and_then(|shredded| shredded.as_opt::<Variant>())
1343            .ok_or_else(|| vortex_err!("expected nested variant shredded child"))?;
1344        let nested_struct = nested_variant
1345            .shredded()
1346            .and_then(|shredded| shredded.as_opt::<Struct>())
1347            .ok_or_else(|| vortex_err!("expected nested struct shredded child"))?;
1348        let value = nested_struct.unmasked_field_by_name("value")?;
1349
1350        assert!(value.is::<Primitive>());
1351        assert!(!value.is::<Constant>());
1352
1353        Ok(())
1354    }
1355}