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