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