This is an automated email from the ASF dual-hosted git repository.

Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 7d6a864920 fix(arrow-data): new_null on an empty union (#11258)
7d6a864920 is described below

commit 7d6a86492009ee3a27eac98b251c846790225e16
Author: pawan <[email protected]>
AuthorDate: Tue Sep 29 14:08:07 2026 +0530

    fix(arrow-data): new_null on an empty union (#11258)
    
    ### Which issue does this PR close?
    
    - Closes #11197.
    
    ### Rationale for this change
    
    new_null took the first union field without checking there was one, so
    an empty union panicked on the unwrap.
    
    len 0 is the only length an empty union can represent. every slot
    carries a type id naming one of the children, and UnionArray::try_new
    rejects a type id that names none.
    
    ### What changes are included in this PR?
    
    an empty union at len 0 builds the empty buffers, type ids plus offsets
    when dense, and no children.
    
    any other length panics saying what is missing, the same shape as
    FixedSizeBinary with a negative width. new_null returns Self, so the
    alternative is data that fails later in validate_full.
    
    ### Are these changes tested?
    
    yes. test_new_null_empty_union covers both modes at len 0 and runs
    validate_full, test_new_null_empty_union_with_slots pins the panic.
    arrow-data is 63 + 13 green, arrow-array 725, clippy clean.
    
    ### Are there any user-facing changes?
    
    new_null on an empty union of length 0 returns an array instead of
    panicking. no api changes.
---
 arrow-data/src/data.rs | 82 ++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 59 insertions(+), 23 deletions(-)

diff --git a/arrow-data/src/data.rs b/arrow-data/src/data.rs
index 4128bffdd3..065ac59846 100644
--- a/arrow-data/src/data.rs
+++ b/arrow-data/src/data.rs
@@ -786,31 +786,46 @@ impl ArrayData {
                     vec![ArrayData::new_empty(v.as_ref())],
                     true,
                 ),
-                DataType::Union(f, mode) => {
-                    let (id, _) = f.iter().next().unwrap();
-                    let ids = Buffer::from_iter(std::iter::repeat_n(id, len));
-                    let buffers = match mode {
-                        UnionMode::Sparse => vec![ids],
-                        UnionMode::Dense => {
-                            let end_offset = i32::from_usize(len).unwrap();
-                            vec![ids, Buffer::from_iter(0_i32..end_offset)]
-                        }
-                    };
-
-                    let children = f
-                        .iter()
-                        .enumerate()
-                        .map(|(idx, (_, f))| {
-                            if idx == 0 || *mode == UnionMode::Sparse {
-                                Self::new_null(f.data_type(), len)
-                            } else {
-                                Self::new_empty(f.data_type())
+                DataType::Union(f, mode) => match f.iter().next() {
+                    // Every slot carries a type id naming one of the children,
+                    // so an empty union has nothing to put in a slot and can
+                    // only be the empty array.
+                    None => {
+                        assert_eq!(
+                            len, 0,
+                            "cannot construct null data from an empty union of 
length {len}, a slot has no type id to carry"
+                        );
+                        let buffers = match mode {
+                            UnionMode::Sparse => vec![zeroed(0)],
+                            UnionMode::Dense => vec![zeroed(0), zeroed(0)],
+                        };
+                        (buffers, vec![], false)
+                    }
+                    Some((id, _)) => {
+                        let ids = Buffer::from_iter(std::iter::repeat_n(id, 
len));
+                        let buffers = match mode {
+                            UnionMode::Sparse => vec![ids],
+                            UnionMode::Dense => {
+                                let end_offset = i32::from_usize(len).unwrap();
+                                vec![ids, Buffer::from_iter(0_i32..end_offset)]
                             }
-                        })
-                        .collect();
+                        };
 
-                    (buffers, children, false)
-                }
+                        let children = f
+                            .iter()
+                            .enumerate()
+                            .map(|(idx, (_, f))| {
+                                if idx == 0 || *mode == UnionMode::Sparse {
+                                    Self::new_null(f.data_type(), len)
+                                } else {
+                                    Self::new_empty(f.data_type())
+                                }
+                            })
+                            .collect();
+
+                        (buffers, children, false)
+                    }
+                },
                 DataType::RunEndEncoded(r, v) => {
                     if len == 0 {
                         // For empty arrays, create zero-length child arrays.
@@ -2440,6 +2455,8 @@ pub(crate) fn get_fixed_size_binary_width(data_type: 
&DataType) -> usize {
 
 #[cfg(test)]
 mod tests {
+    use arrow_schema::UnionFields;
+
     use super::*;
     use crate::ByteView;
     use crate::transform::MutableArrayData;
@@ -3677,4 +3694,23 @@ mod tests {
 
         [from_builder_res, from_try_new_res]
     }
+
+    #[test]
+    fn test_new_null_empty_union() {
+        for mode in [UnionMode::Sparse, UnionMode::Dense] {
+            let data_type = DataType::Union(UnionFields::empty(), mode);
+            let data = ArrayData::new_null(&data_type, 0);
+            data.validate_full()
+                .expect("an empty union of length zero is valid");
+            assert_eq!(data.len(), 0);
+            assert!(data.child_data().is_empty());
+        }
+    }
+
+    #[test]
+    #[should_panic(expected = "cannot construct null data from an empty 
union")]
+    fn test_new_null_empty_union_with_slots() {
+        let data_type = DataType::Union(UnionFields::empty(), 
UnionMode::Dense);
+        let _ = ArrayData::new_null(&data_type, 1);
+    }
 }

Reply via email to