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