Skip to main content

vortex_array/arrays/union/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::iter::once;
5use std::sync::Arc;
6
7use vortex_error::VortexExpect;
8use vortex_error::VortexResult;
9use vortex_error::vortex_ensure;
10use vortex_error::vortex_err;
11
12use crate::ArrayRef;
13use crate::ArraySlots;
14use crate::IntoArray;
15use crate::array::Array;
16use crate::array::ArrayParts;
17use crate::array::EmptyArrayData;
18use crate::array::TypedArrayRef;
19use crate::arrays::PrimitiveArray;
20use crate::arrays::Union;
21use crate::dtype::DType;
22use crate::dtype::Nullability;
23use crate::dtype::PType;
24use crate::dtype::UnionVariants;
25
26/// The row-aligned array of type IDs selecting a union child.
27pub(super) const TYPE_IDS_SLOT: usize = 0;
28/// The offset at which the sparse child arrays begin in the slots vector.
29pub(super) const CHILDREN_OFFSET: usize = 1;
30
31pub(super) fn make_union_parts(
32    type_ids: ArrayRef,
33    variants: UnionVariants,
34    children: &[ArrayRef],
35) -> ArrayParts<Union> {
36    let len = type_ids.len();
37    let nullability = type_ids.dtype().nullability();
38    let slots: ArraySlots = once(Some(type_ids))
39        .chain(children.iter().cloned().map(Some))
40        .collect();
41
42    ArrayParts::new(
43        Union,
44        DType::Union(variants, nullability),
45        len,
46        EmptyArrayData,
47    )
48    .with_slots(slots)
49}
50
51/// Concrete parts of a [`UnionArray`](super::UnionArray).
52pub struct UnionDataParts {
53    /// The union variant schema.
54    pub variants: UnionVariants,
55    /// The row-aligned type IDs.
56    pub type_ids: ArrayRef,
57    /// The row-aligned sparse children in variant order.
58    pub children: Arc<[ArrayRef]>,
59}
60
61/// Accessors for a canonical sparse union array.
62pub trait UnionArrayExt: TypedArrayRef<Union> {
63    /// The union's variant schema.
64    fn variants(&self) -> &UnionVariants {
65        match self.as_ref().dtype() {
66            DType::Union(variants, _) => variants,
67            _ => unreachable!("UnionArrayExt requires a union dtype"),
68        }
69    }
70
71    /// The row-aligned `u8` type IDs whose nulls represent outer union nulls.
72    fn type_ids(&self) -> &ArrayRef {
73        self.as_ref().slots()[TYPE_IDS_SLOT]
74            .as_ref()
75            .vortex_expect("UnionArray type_ids slot")
76    }
77
78    /// Iterate over sparse children in variant order.
79    fn iter_children(&self) -> impl ExactSizeIterator<Item = &ArrayRef> + '_ {
80        self.as_ref().slots()[CHILDREN_OFFSET..]
81            .iter()
82            .map(|slot| slot.as_ref().vortex_expect("UnionArray child slot"))
83    }
84
85    /// Return the sparse children in variant order.
86    fn children(&self) -> Arc<[ArrayRef]> {
87        self.iter_children().cloned().collect()
88    }
89
90    /// Return a sparse child by its variant index.
91    fn child(&self, index: usize) -> Option<&ArrayRef> {
92        self.as_ref().slots().get(CHILDREN_OFFSET + index)?.as_ref()
93    }
94
95    /// Return a sparse child selected by a data-level type ID.
96    fn child_by_type_id(&self, type_id: u8) -> Option<&ArrayRef> {
97        self.child(self.variants().tag_to_child_index(type_id)?)
98    }
99
100    /// Return a sparse child selected by its variant name, if present.
101    fn child_by_name_opt(&self, name: impl AsRef<str>) -> Option<&ArrayRef> {
102        self.child(self.variants().find(name)?)
103    }
104
105    /// Return a sparse child selected by its variant name.
106    fn child_by_name(&self, name: impl AsRef<str>) -> VortexResult<&ArrayRef> {
107        let name = name.as_ref();
108        self.child_by_name_opt(name).ok_or_else(|| {
109            vortex_err!(
110                "Variant {name} not found in union array with names {:?}",
111                self.variants().names()
112            )
113        })
114    }
115}
116impl<T: TypedArrayRef<Union>> UnionArrayExt for T {}
117
118impl Array<Union> {
119    /// Construct a canonical sparse union array.
120    ///
121    /// # Panics
122    ///
123    /// Panics if the components do not satisfy the invariants documented on
124    /// [`Self::new_unchecked`].
125    pub fn new(
126        type_ids: ArrayRef,
127        variants: UnionVariants,
128        children: impl Into<Arc<[ArrayRef]>>,
129    ) -> Self {
130        Self::try_new(type_ids, variants, children).vortex_expect("UnionArray construction failed")
131    }
132
133    /// Try to construct a canonical sparse union array.
134    ///
135    /// Type ID values are not validated during construction. Accessing a non-null row whose type
136    /// ID is not declared by `variants` will panic.
137    ///
138    /// # Errors
139    ///
140    /// Returns an error if `type_ids` is not a `u8` array, or if the sparse children do not match
141    /// the variant schema and outer array length.
142    pub fn try_new(
143        type_ids: ArrayRef,
144        variants: UnionVariants,
145        children: impl Into<Arc<[ArrayRef]>>,
146    ) -> VortexResult<Self> {
147        vortex_ensure!(
148            matches!(type_ids.dtype(), DType::Primitive(PType::U8, _)),
149            "UnionArray type_ids must be u8, got {}",
150            type_ids.dtype()
151        );
152
153        let children = children.into();
154        Array::try_from_parts(make_union_parts(type_ids, variants, &children))
155    }
156
157    /// Construct a canonical sparse union array without validation.
158    ///
159    /// # Safety
160    ///
161    /// The caller must ensure `type_ids` is a `u8` array, every child has the corresponding variant
162    /// dtype, and all arrays have the same length. Null type IDs represent outer union nulls.
163    pub unsafe fn new_unchecked(
164        type_ids: ArrayRef,
165        variants: UnionVariants,
166        children: impl Into<Arc<[ArrayRef]>>,
167    ) -> Self {
168        let children = children.into();
169        unsafe { Array::from_parts_unchecked(make_union_parts(type_ids, variants, &children)) }
170    }
171
172    /// Deconstruct this array into its type IDs, variant schema, and sparse children.
173    pub fn into_data_parts(self) -> UnionDataParts {
174        let variants = self.variants().clone();
175        let type_ids = self.type_ids().clone();
176        let children = self.children();
177        UnionDataParts {
178            variants,
179            type_ids,
180            children,
181        }
182    }
183
184    /// Create an empty array for a union dtype.
185    pub(crate) fn empty(variants: UnionVariants, nullability: Nullability) -> Self {
186        let type_ids = PrimitiveArray::empty::<u8>(nullability).into_array();
187        let children: Vec<_> = variants
188            .variants()
189            .map(|dtype| crate::Canonical::empty(&dtype).into_array())
190            .collect();
191
192        Self::new(type_ids, variants, children)
193    }
194}