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(