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);
+ }
}