Skip to content

Commit 1e867dd

Browse files
committed
Add UnionArray compute functions
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 3233233 commit 1e867dd

20 files changed

Lines changed: 715 additions & 59 deletions

File tree

vortex-array/src/aggregate_fn/fns/uncompressed_size_in_bytes/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod list_view;
99
mod null;
1010
mod primitive;
1111
mod struct_;
12+
mod union;
1213
mod varbinview;
1314

1415
use std::mem::size_of;
@@ -21,6 +22,7 @@ use list_view::list_view_uncompressed_size_in_bytes;
2122
use null::null_uncompressed_size_in_bytes;
2223
use primitive::primitive_uncompressed_size_in_bytes;
2324
use struct_::struct_uncompressed_size_in_bytes;
25+
use union::union_uncompressed_size_in_bytes;
2426
use varbinview::varbinview_uncompressed_size_in_bytes;
2527
use vortex_error::VortexExpect;
2628
use vortex_error::VortexResult;
@@ -199,9 +201,7 @@ pub(crate) fn canonical_uncompressed_size_in_bytes(
199201
Canonical::List(array) => list_view_uncompressed_size_in_bytes(array, ctx),
200202
Canonical::FixedSizeList(array) => fixed_size_list_uncompressed_size_in_bytes(array, ctx),
201203
Canonical::Struct(array) => struct_uncompressed_size_in_bytes(array, ctx),
202-
Canonical::Union(_) => {
203-
todo!("TODO(connor)[Union]: implement UncompressedSizeInBytes for Union arrays")
204-
}
204+
Canonical::Union(array) => union_uncompressed_size_in_bytes(array, ctx),
205205
Canonical::Extension(array) => extension_uncompressed_size_in_bytes(array, ctx),
206206
Canonical::Variant(_) => {
207207
vortex_bail!("UncompressedSizeInBytes is not supported for Variant arrays")
@@ -232,11 +232,14 @@ pub(crate) fn constant_uncompressed_size_in_bytes(
232232
array.len(),
233233
array.scalar().as_binary().value().map(|value| value.len()),
234234
)?,
235-
DType::List(..) | DType::FixedSizeList(..) | DType::Struct(..) | DType::Extension(_) => {
235+
DType::List(..)
236+
| DType::FixedSizeList(..)
237+
| DType::Struct(..)
238+
| DType::Union(..)
239+
| DType::Extension(_) => {
236240
let canonical = array.array().clone().execute::<Canonical>(ctx)?;
237241
return canonical_uncompressed_size_in_bytes(&canonical, ctx);
238242
}
239-
DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
240243
DType::Variant(_) => {
241244
vortex_bail!("UncompressedSizeInBytes is not supported for Variant arrays")
242245
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use vortex_error::VortexResult;
5+
use vortex_error::vortex_err;
6+
7+
use super::uncompressed_size_in_bytes_u64;
8+
use crate::ExecutionCtx;
9+
use crate::arrays::UnionArray;
10+
use crate::arrays::union::UnionArrayExt;
11+
12+
pub(super) fn union_uncompressed_size_in_bytes(
13+
array: &UnionArray,
14+
ctx: &mut ExecutionCtx,
15+
) -> VortexResult<u64> {
16+
let mut size = uncompressed_size_in_bytes_u64(array.type_ids(), ctx)?;
17+
18+
for child in array.iter_children() {
19+
size = size
20+
.checked_add(uncompressed_size_in_bytes_u64(child, ctx)?)
21+
.ok_or_else(|| vortex_err!("uncompressed size in bytes overflowed u64"))?;
22+
}
23+
24+
Ok(size)
25+
}

vortex-array/src/arrays/chunked/vtable/canonical.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ use crate::arrays::FixedSizeListArray;
1919
use crate::arrays::ListViewArray;
2020
use crate::arrays::PrimitiveArray;
2121
use crate::arrays::StructArray;
22+
use crate::arrays::UnionArray;
2223
use crate::arrays::VariantArray;
2324
use crate::arrays::chunked::ChunkedArrayExt;
2425
use crate::arrays::fixed_size_list::FixedSizeListArrayExt;
2526
use crate::arrays::listview::ListViewArrayExt;
2627
use crate::arrays::listview::ListViewRebuildMode;
28+
use crate::arrays::union::UnionArrayExt;
29+
use crate::arrays::union::type_ids_dtype;
2730
use crate::arrays::variant::VariantArrayExt;
2831
use crate::builders::builder_with_capacity_in;
2932
use crate::builtins::ArrayBuiltins;
@@ -54,6 +57,7 @@ pub(super) fn _canonicalize(
5457
let struct_array = pack_struct_chunks(owned_chunks, ctx)?;
5558
Canonical::Struct(struct_array)
5659
}
60+
DType::Union(..) => Canonical::Union(pack_union_chunks(owned_chunks, ctx)?),
5761
DType::List(elem_dtype, _) => Canonical::List(swizzle_list_chunks(
5862
&owned_chunks,
5963
array.array().validity()?,
@@ -89,6 +93,46 @@ fn pack_struct_chunks(chunks: Vec<ArrayRef>, ctx: &mut ExecutionCtx) -> VortexRe
8993
.process_results(|iter| StructArray::try_concat(iter))?
9094
}
9195

96+
/// Packs sparse union chunks into one union with chunked type IDs and sparse children.
97+
fn pack_union_chunks(chunks: Vec<ArrayRef>, ctx: &mut ExecutionCtx) -> VortexResult<UnionArray> {
98+
let union_chunks = chunks
99+
.into_iter()
100+
.map(|chunk| chunk.execute::<UnionArray>(ctx))
101+
.collect::<VortexResult<Vec<_>>>()?;
102+
let variants = union_chunks[0].variants().clone();
103+
let type_ids = ChunkedArray::try_new(
104+
union_chunks
105+
.iter()
106+
.map(|chunk| chunk.type_ids().clone())
107+
.collect(),
108+
type_ids_dtype(union_chunks[0].dtype().nullability()),
109+
)?
110+
.into_array();
111+
let children = variants
112+
.variants()
113+
.enumerate()
114+
.map(|(index, dtype)| {
115+
ChunkedArray::try_new(
116+
union_chunks
117+
.iter()
118+
.map(|chunk| {
119+
chunk
120+
.child(index)
121+
.vortex_expect("variant index must have a sparse child")
122+
.clone()
123+
})
124+
.collect(),
125+
dtype,
126+
)
127+
.map(IntoArray::into_array)
128+
})
129+
.collect::<VortexResult<Vec<_>>>()?;
130+
131+
// SAFETY: Every component was taken from validated UnionArrays with the same dtype and
132+
// chunked along identical row boundaries.
133+
Ok(unsafe { UnionArray::new_unchecked(type_ids, variants, children) })
134+
}
135+
92136
/// Packs many [`VariantArray`]s into one [`VariantArray`] with chunked children.
93137
///
94138
/// The caller guarantees there are at least 2 chunks.

vortex-array/src/arrays/chunked/vtable/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,12 @@ impl VTable for Chunked {
251251

252252
fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
253253
match array.dtype() {
254-
// Struct, List, FixedSizeList, and Variant need child swizzling that the builder path
255-
// cannot express.
256-
DType::Struct(..) | DType::List(..) | DType::FixedSizeList(..) | DType::Variant(..) => {
254+
// Nested canonical arrays need child swizzling that the builder path cannot express.
255+
DType::Struct(..)
256+
| DType::List(..)
257+
| DType::FixedSizeList(..)
258+
| DType::Union(..)
259+
| DType::Variant(..) => {
257260
// TODO(joe)[#7674]: iterative execution here too
258261
Ok(ExecutionResult::done(_canonicalize(array.as_view(), ctx)?))
259262
}

vortex-array/src/arrays/constant/vtable/canonical.rs

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use vortex_buffer::Buffer;
88
use vortex_buffer::buffer;
99
use vortex_error::VortexExpect;
1010
use vortex_error::VortexResult;
11+
use vortex_error::vortex_err;
1112

1213
use crate::Canonical;
1314
use crate::ExecutionCtx;
@@ -23,13 +24,16 @@ use crate::arrays::ListViewArray;
2324
use crate::arrays::NullArray;
2425
use crate::arrays::PrimitiveArray;
2526
use crate::arrays::StructArray;
27+
use crate::arrays::UnionArray;
2628
use crate::arrays::VarBinViewArray;
2729
use crate::arrays::VariantArray;
2830
use crate::arrays::varbinview::BinaryView;
2931
use crate::builders::builder_with_capacity;
3032
use crate::dtype::DType;
3133
use crate::dtype::DecimalType;
3234
use crate::dtype::Nullability;
35+
use crate::dtype::PType;
36+
use crate::dtype::UnionVariants;
3337
use crate::match_each_decimal_value;
3438
use crate::match_each_decimal_value_type;
3539
use crate::match_each_native_ptype;
@@ -146,16 +150,22 @@ pub(crate) fn constant_canonicalize(
146150
.collect(),
147151
None => {
148152
assert!(matches!(validity, Validity::AllInvalid));
149-
// The struct is entirely null, so fields just need placeholder values with the
150-
// correct dtype. We use `default_value` which returns a zero for non-nullable
151-
// dtypes and null for nullable dtypes, preserving each field's nullability.
152153
struct_dtype
153154
.fields()
154155
.map(|dt| {
155-
let scalar = Scalar::default_value(&dt);
156-
ConstantArray::new(scalar, array.len()).into_array()
156+
if array.is_empty() {
157+
return Ok(Canonical::empty(&dt).into_array());
158+
}
159+
160+
let scalar = try_placeholder_scalar(&dt).ok_or_else(|| {
161+
vortex_err!(
162+
"cannot canonicalize null constant struct: field dtype {dt} \
163+
has no placeholder value"
164+
)
165+
})?;
166+
Ok(ConstantArray::new(scalar, array.len()).into_array())
157167
})
158-
.collect()
168+
.collect::<VortexResult<Vec<_>>>()?
159169
}
160170
};
161171
// SAFETY: Fields are constructed from the same struct scalar, all have same
@@ -164,7 +174,12 @@ pub(crate) fn constant_canonicalize(
164174
StructArray::new_unchecked(fields, struct_dtype.clone(), array.len(), validity)
165175
})
166176
}
167-
DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
177+
DType::Union(variants, nullability) => Canonical::Union(constant_canonical_union(
178+
scalar,
179+
variants,
180+
*nullability,
181+
array.len(),
182+
)?),
168183
DType::Variant(_) => Canonical::Variant(VariantArray::try_new(
169184
array.array().clone().into_array(),
170185
None,
@@ -188,6 +203,97 @@ pub(crate) fn constant_canonicalize(
188203
})
189204
}
190205

206+
fn constant_canonical_union(
207+
scalar: &Scalar,
208+
variants: &UnionVariants,
209+
nullability: Nullability,
210+
len: usize,
211+
) -> VortexResult<UnionArray> {
212+
if len == 0 {
213+
return Ok(UnionArray::empty(variants.clone(), nullability));
214+
}
215+
216+
let union = scalar.as_union();
217+
let selected = union.child_index().zip(union.child());
218+
219+
let children = variants
220+
.variants()
221+
.enumerate()
222+
.map(|(index, dtype)| {
223+
let value = match &selected {
224+
Some((selected_child, selected_value)) if index == *selected_child => {
225+
selected_value.clone()
226+
}
227+
_ => try_placeholder_scalar(&dtype).ok_or_else(|| {
228+
vortex_err!(
229+
"cannot canonicalize constant union: unselected variant {} ({dtype}) has \
230+
no placeholder value",
231+
variants.names()[index]
232+
)
233+
})?,
234+
};
235+
Ok(ConstantArray::new(value, len).into_array())
236+
})
237+
.collect::<VortexResult<Vec<_>>>()?;
238+
239+
let type_id = match union.type_id() {
240+
Some(type_id) => Scalar::primitive(type_id, nullability),
241+
None => Scalar::null(DType::Primitive(PType::U8, Nullability::Nullable)),
242+
};
243+
244+
UnionArray::try_new(
245+
ConstantArray::new(type_id, len).into_array(),
246+
variants.clone(),
247+
children,
248+
)
249+
}
250+
251+
/// Builds an arbitrary valid scalar for array positions ignored by a parent validity or type ID.
252+
/// This does not give the dtype a semantic default value.
253+
fn try_placeholder_scalar(dtype: &DType) -> Option<Scalar> {
254+
if let Some(default) = Scalar::try_default_value(dtype) {
255+
return Some(default);
256+
}
257+
258+
match dtype {
259+
DType::FixedSizeList(element_dtype, size, nullability) => {
260+
let element = try_placeholder_scalar(element_dtype)?;
261+
Some(Scalar::fixed_size_list(
262+
Arc::clone(element_dtype),
263+
vec![element; *size as usize],
264+
*nullability,
265+
))
266+
}
267+
DType::Struct(fields, _) => {
268+
let children = fields
269+
.fields()
270+
.map(|field| try_placeholder_scalar(&field))
271+
.collect::<Option<Vec<_>>>()?;
272+
Some(Scalar::struct_(dtype.clone(), children))
273+
}
274+
DType::Union(variants, nullability) => {
275+
variants
276+
.variants()
277+
.enumerate()
278+
.find_map(|(index, child_dtype)| {
279+
let child = try_placeholder_scalar(&child_dtype)?;
280+
Scalar::union(
281+
variants.clone(),
282+
variants.child_index_to_tag(index),
283+
child,
284+
*nullability,
285+
)
286+
.ok()
287+
})
288+
}
289+
DType::Extension(ext_dtype) => {
290+
let storage = try_placeholder_scalar(ext_dtype.storage_dtype())?;
291+
Some(Scalar::extension_ref(ext_dtype.clone(), storage))
292+
}
293+
_ => None,
294+
}
295+
}
296+
191297
fn constant_canonical_byte_view(
192298
scalar_bytes: Option<&[u8]>,
193299
dtype: &DType,

vortex-array/src/arrays/dict/execute.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::arrays::VarBinViewArray;
3030
use crate::arrays::VariantArray;
3131
use crate::arrays::dict::TakeExecute;
3232
use crate::arrays::dict::TakeReduce;
33+
use crate::arrays::union::compute::take_union;
3334
use crate::arrays::variant::VariantArrayExt;
3435

3536
/// Take from a canonical array using indices (codes), returning a new canonical array.
@@ -53,8 +54,9 @@ pub(crate) fn take_canonical(
5354
Canonical::FixedSizeList(take_fixed_size_list(&a, codes, ctx))
5455
}
5556
Canonical::Struct(a) => Canonical::Struct(take_struct(&a, codes)),
56-
Canonical::Union(_) => {
57-
todo!("TODO(connor)[Union]: implement dictionary execution for Union arrays")
57+
Canonical::Union(a) => {
58+
let indices = codes.clone().into_array();
59+
Canonical::Union(take_union(a.as_view(), &indices)?)
5860
}
5961
Canonical::Extension(a) => Canonical::Extension(take_extension(&a, codes, ctx)),
6062
Canonical::Variant(a) => {

vortex-array/src/arrays/filter/execute/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::arrays::NullArray;
2424
use crate::arrays::VariantArray;
2525
use crate::arrays::extension::ExtensionArrayExt;
2626
use crate::arrays::filter::FilterArrayExt;
27+
use crate::arrays::union::compute::filter_union;
2728
use crate::arrays::variant::VariantArrayExt;
2829
use crate::scalar::Scalar;
2930
use crate::validity::Validity;
@@ -95,8 +96,12 @@ pub(super) fn execute_filter(canonical: Canonical, mask: &Arc<MaskValues>) -> Ca
9596
Canonical::FixedSizeList(fixed_size_list::filter_fixed_size_list(&a, mask))
9697
}
9798
Canonical::Struct(a) => Canonical::Struct(struct_::filter_struct(&a, mask)),
98-
Canonical::Union(_) => {
99-
todo!("TODO(connor)[Union]: implement filter for Union arrays")
99+
Canonical::Union(a) => {
100+
let mask = Mask::Values(Arc::clone(mask));
101+
Canonical::Union(
102+
filter_union(a.as_view(), &mask)
103+
.vortex_expect("UnionArray children must support filter"),
104+
)
100105
}
101106
Canonical::Extension(a) => {
102107
let filtered_storage = a

0 commit comments

Comments
 (0)