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 vortex_error::VortexExpect;
5use vortex_error::VortexResult;
6use vortex_error::vortex_ensure;
7use vortex_error::vortex_err;
8
9use crate::ArrayRef;
10use crate::ArraySlots;
11use crate::IntoArray;
12use crate::array::Array;
13use crate::array::ArrayParts;
14use crate::array::EmptyArrayData;
15use crate::array::TypedArrayRef;
16use crate::array_slots;
17use crate::arrays::ConstantArray;
18use crate::arrays::PrimitiveArray;
19use crate::arrays::Union;
20use crate::arrays::union::union_type_ids_dtype;
21use crate::dtype::DType;
22use crate::dtype::Nullability;
23use crate::dtype::PType;
24use crate::dtype::UnionVariants;
25use crate::scalar::Scalar;
26
27/// Slot layout of a canonical sparse union array.
28#[array_slots(Union)]
29pub struct UnionSlots {
30    /// The row-aligned array of type IDs selecting a union child.
31    #[slot(0)]
32    pub type_ids: ArrayRef,
33    /// The row-aligned sparse children in variant order.
34    #[slot(1..)]
35    pub children: Vec<ArrayRef>,
36}
37
38pub(super) fn make_union_parts(
39    type_ids: ArrayRef,
40    variants: UnionVariants,
41    children: impl IntoIterator<Item = ArrayRef>,
42) -> ArrayParts<Union> {
43    let len = type_ids.len();
44    let nullability = type_ids.dtype().nullability();
45    let children = children.into_iter();
46    let (lower, _) = children.size_hint();
47    let mut slots = ArraySlots::with_capacity(UnionSlots::CHILDREN_OFFSET + lower);
48    slots.push(Some(type_ids));
49    slots.extend(children.map(Some));
50
51    ArrayParts::new(
52        Union,
53        DType::Union(variants, nullability),
54        len,
55        EmptyArrayData,
56    )
57    .with_slots(slots)
58}
59
60/// Concrete parts of a [`UnionArray`](super::UnionArray).
61pub struct UnionDataParts {
62    /// The union variant schema.
63    pub variants: UnionVariants,
64    /// The row-aligned type IDs.
65    pub type_ids: ArrayRef,
66    /// The row-aligned sparse children in variant order.
67    pub children: Vec<ArrayRef>,
68}
69
70/// Accessors for a canonical sparse union array.
71///
72/// Slot accessors (`type_ids`, `children`, `slots_view`) live on the generated
73/// [`UnionArraySlotsExt`] supertrait; this trait layers union-specific lookups on top.
74pub trait UnionArrayExt: UnionArraySlotsExt {
75    /// The union's variant schema.
76    fn variants(&self) -> &UnionVariants {
77        match self.as_ref().dtype() {
78            DType::Union(variants, _) => variants,
79            _ => unreachable!("UnionArrayExt requires a union dtype"),
80        }
81    }
82
83    /// Iterate over sparse children in variant order.
84    fn iter_children(&self) -> impl ExactSizeIterator<Item = &ArrayRef> + '_ {
85        self.children().iter()
86    }
87
88    /// Return a sparse child by its variant index.
89    fn child(&self, index: usize) -> Option<&ArrayRef> {
90        self.children().get(index)
91    }
92
93    /// Return a sparse child selected by a data-level type ID.
94    fn child_by_type_id(&self, type_id: u8) -> Option<&ArrayRef> {
95        self.child(self.variants().tag_to_child_index(type_id)?)
96    }
97
98    /// Return a sparse child selected by its variant name, if present.
99    fn child_by_name_opt(&self, name: impl AsRef<str>) -> Option<&ArrayRef> {
100        self.child(self.variants().find(name)?)
101    }
102
103    /// Return a sparse child selected by its variant name.
104    fn child_by_name(&self, name: impl AsRef<str>) -> VortexResult<&ArrayRef> {
105        let name = name.as_ref();
106        self.child_by_name_opt(name).ok_or_else(|| {
107            vortex_err!(
108                "Variant {name} not found in union array with names {:?}",
109                self.variants().names()
110            )
111        })
112    }
113}
114impl<T: TypedArrayRef<Union>> UnionArrayExt for T {}
115
116impl Array<Union> {
117    /// Construct a canonical sparse union array.
118    ///
119    /// # Panics
120    ///
121    /// Panics if the components do not satisfy the invariants documented on
122    /// [`Self::new_unchecked`].
123    pub fn new(
124        type_ids: ArrayRef,
125        variants: UnionVariants,
126        children: impl IntoIterator<Item = ArrayRef>,
127    ) -> Self {
128        Self::try_new(type_ids, variants, children).vortex_expect("UnionArray construction failed")
129    }
130
131    /// Try to construct a canonical sparse union array.
132    ///
133    /// Type ID values are not validated during construction. Accessing a non-null row whose type
134    /// ID is not declared by `variants` will panic.
135    ///
136    /// # Errors
137    ///
138    /// Returns an error if `type_ids` is not a `u8` array, or if the sparse children do not match
139    /// the variant schema and outer array length.
140    pub fn try_new(
141        type_ids: ArrayRef,
142        variants: UnionVariants,
143        children: impl IntoIterator<Item = ArrayRef>,
144    ) -> VortexResult<Self> {
145        vortex_ensure!(
146            matches!(type_ids.dtype(), DType::Primitive(PType::U8, _)),
147            "UnionArray type_ids must be u8, got {}",
148            type_ids.dtype()
149        );
150
151        Array::try_from_parts(make_union_parts(type_ids, variants, children))
152    }
153
154    /// Construct a canonical sparse union array without validation.
155    ///
156    /// # Safety
157    ///
158    /// The caller must ensure `type_ids` is a `u8` array, every child has the corresponding variant
159    /// dtype, and all arrays have the same length. Null type IDs represent outer union nulls.
160    pub unsafe fn new_unchecked(
161        type_ids: ArrayRef,
162        variants: UnionVariants,
163        children: impl IntoIterator<Item = ArrayRef>,
164    ) -> Self {
165        unsafe { Array::from_parts_unchecked(make_union_parts(type_ids, variants, children)) }
166    }
167
168    /// Deconstruct this array into its type IDs, variant schema, and sparse children.
169    pub fn into_data_parts(self) -> UnionDataParts {
170        let variants = self.variants().clone();
171        let type_ids = self.type_ids().clone();
172        let children = self.iter_children().cloned().collect();
173        UnionDataParts {
174            variants,
175            type_ids,
176            children,
177        }
178    }
179
180    /// Construct a `len`-row union in which every row holds `scalar`.
181    ///
182    /// Unselected children are filled with their variant's default value, a null for a nullable
183    /// variant and a zero for a non-nullable one. An outer null `scalar` selects no child at all.
184    ///
185    /// # Errors
186    ///
187    /// Returns an error if `scalar` does not have a union dtype.
188    pub fn constant(scalar: &Scalar, len: usize) -> VortexResult<Self> {
189        let union = scalar
190            .as_union_opt()
191            .ok_or_else(|| vortex_err!("Expected a union scalar, got {}", scalar.dtype()))?;
192        let variants = union.variants().clone();
193        let nullability = union.nullability();
194
195        let type_ids = match union.type_id() {
196            Some(type_id) => Scalar::primitive(type_id, nullability),
197            None => Scalar::null(union_type_ids_dtype(nullability)),
198        };
199
200        let selected = union.child_index().zip(union.child());
201
202        let children = variants
203            .variants()
204            .enumerate()
205            .map(|(index, dtype)| {
206                let value = match &selected {
207                    Some((selected_index, child)) if *selected_index == index => child.clone(),
208                    _ => Scalar::default_value(&dtype),
209                };
210
211                ConstantArray::new(value, len).into_array()
212            })
213            .collect::<Vec<_>>();
214
215        Self::try_new(
216            ConstantArray::new(type_ids, len).into_array(),
217            variants,
218            children,
219        )
220    }
221
222    /// Create an empty array for a union dtype.
223    pub(crate) fn empty(variants: UnionVariants, nullability: Nullability) -> Self {
224        let type_ids = PrimitiveArray::empty::<u8>(nullability).into_array();
225        let children: Vec<_> = variants
226            .variants()
227            .map(|dtype| crate::Canonical::empty(&dtype).into_array())
228            .collect();
229
230        Self::new(type_ids, variants, children)
231    }
232}