You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by gi...@apache.org on 2026/10/18 03:48:08 UTC
(arrow-rs) 01/01: Fix sliced sparse union ArrayData conversion (#11649)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11649-f53b221bd6a002d6ca1ed4775b695e1df51d087f
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit a9341121687aea7881063ff07b88c14967ee9a9d
Author: Kyon <25...@users.noreply.github.com>
AuthorDate: Sun Oct 18 03:24:16 2026 +0000
Fix sliced sparse union ArrayData conversion (#11649)
# Which issue does this PR close?
- Closes #11582
# Rationale for this change
Converting sliced sparse union `ArrayData` into an array currently reads
values outside the slice. A single-child union can also return a logical
null mask longer than the resulting array.
# What changes are included in this PR?
Apply the parent offset and length to sparse child data before
constructing the child arrays. Dense children retain their existing
offsets and lengths.
# Are these changes tested?
Regression tests compare conversion from sliced `ArrayData` with direct
union slicing, including nonzero offsets, truncation at offset zero,
empty slices, custom type IDs, nullable values, and a dense-union
control.
AI assistance was used to analyze the conversion path and draft the
code, regression tests, and this description.
# Are there any significant user-facing changes?
No public API changes.
---
arrow-array/src/array/union_array.rs | 53 ++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/arrow-array/src/array/union_array.rs b/arrow-array/src/array/union_array.rs
index bdac19272e..e6a25101b2 100644
--- a/arrow-array/src/array/union_array.rs
+++ b/arrow-array/src/array/union_array.rs
@@ -705,6 +705,10 @@ impl From<ArrayData> for UnionArray {
let max_id = fields.iter().map(|(i, _)| i).max().unwrap_or_default() as usize;
let mut boxed_fields = vec![None; max_id + 1];
for (cd, (field_id, _)) in child_data.into_iter().zip(fields.iter()) {
+ let cd = match mode {
+ UnionMode::Sparse => cd.sliced(offset, len),
+ UnionMode::Dense => cd,
+ };
boxed_fields[field_id as usize] = Some(make_array(cd));
}
Self {
@@ -1693,6 +1697,55 @@ mod tests {
test_slice_union(record_batch_slice);
}
+ #[test]
+ fn test_union_from_sliced_array_data() {
+ for mode in [UnionMode::Sparse, UnionMode::Dense] {
+ let fields = UnionFields::try_new(
+ [3, 7],
+ [
+ Field::new("ints", DataType::Int32, false),
+ Field::new("floats", DataType::Float64, false),
+ ],
+ )
+ .unwrap();
+ let offsets = (mode == UnionMode::Dense).then(|| vec![0, 1, 2, 3].into());
+ let array = UnionArray::try_new(
+ fields,
+ vec![3, 7, 3, 7].into(),
+ offsets,
+ vec![
+ Arc::new(Int32Array::from(vec![10, 20, 30, 40])),
+ Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0, 4.0])),
+ ],
+ )
+ .unwrap();
+ for (offset, len) in [(1, 2), (0, 2), (4, 0), (0, 4)] {
+ let expected = array.slice(offset, len);
+ let actual = make_array(array.to_data().slice(offset, len));
+ let actual = actual.as_any().downcast_ref::<UnionArray>().unwrap();
+ for i in 0..len {
+ assert_eq!(actual.value(i).to_data(), expected.value(i).to_data());
+ }
+ assert_eq!(actual.to_data(), expected.to_data());
+ }
+ }
+ }
+
+ #[test]
+ fn test_sparse_union_from_sliced_array_data_logical_nulls() {
+ let child = Int32Array::from(vec![Some(10), None, Some(30)]);
+ let fields =
+ UnionFields::try_new([0], [Field::new("ints", DataType::Int32, true)]).unwrap();
+ let array =
+ UnionArray::try_new(fields, vec![0, 0, 0].into(), None, vec![Arc::new(child)]).unwrap();
+ for (offset, len) in [(0, 1), (1, 1), (2, 1), (3, 0)] {
+ let expected = array.slice(offset, len);
+ let actual = make_array(array.to_data().slice(offset, len));
+ assert_eq!(actual.logical_nulls(), expected.logical_nulls());
+ assert_eq!(actual.to_data(), expected.to_data());
+ }
+ }
+
#[test]
fn test_custom_type_ids() {
let data_type = DataType::Union(