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-11643-a2b405fe22b6657fd4f69eb139e6836cafa3b55f in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 24c53c7b0803d32575e5bcb302bf78065ded31b6 Author: Chang Yong Lik <[email protected]> AuthorDate: Sun Oct 18 02:36:03 2026 +0000 fix(arrow-array): use the field's type id in dense union logical_nulls (#11643) # Which issue does this PR close? - Closes #11595. # Rationale for this change A dense union with a single field whose type id is not 0 reported no logical nulls, so `logical_null_count`, `is_null` and `is_not_null` were wrong for it. # What changes are included in this PR? The single-field path of `UnionArray::logical_nulls` passed the child nulls to `gather_nulls` under type id 0. It now passes the field's own type id, enumerating `self.fields` the same way `fields_logical_nulls` does. # Are these changes tested? Yes, `test_dense_union_one_field_nonzero_type_id_logical_nulls`. It fails without the fix. # Are there any significant user-facing changes? No. AI use: I used Claude Code to find the cause and write the fix and test. I reviewed the change and checked that the test fails without the fix. --- arrow-array/src/array/union_array.rs | 46 ++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/arrow-array/src/array/union_array.rs b/arrow-array/src/array/union_array.rs index 17b740c29c..bdac19272e 100644 --- a/arrow-array/src/array/union_array.rs +++ b/arrow-array/src/array/union_array.rs @@ -795,18 +795,23 @@ unsafe impl Array for UnionArray { }; if fields.len() <= 1 { - return self.fields.iter().find_map(|field_opt| { - field_opt - .as_ref() - .and_then(|field| field.logical_nulls()) - .map(|logical_nulls| { - if self.is_dense() { - self.gather_nulls(vec![(0, logical_nulls)]).into() - } else { - logical_nulls - } - }) - }); + return self + .fields + .iter() + .enumerate() + .find_map(|(type_id, field_opt)| { + field_opt + .as_ref() + .and_then(|field| field.logical_nulls()) + .map(|logical_nulls| { + if self.is_dense() { + self.gather_nulls(vec![(type_id as i8, logical_nulls)]) + .into() + } else { + logical_nulls + } + }) + }); } let logical_nulls = self.fields_logical_nulls(); @@ -2050,6 +2055,23 @@ mod tests { assert_eq!(expected, array.gather_nulls(array.fields_logical_nulls())); } + #[test] + fn test_dense_union_one_field_nonzero_type_id_logical_nulls() { + let fields = UnionFields::try_new([5], [Field::new("a", DataType::Int32, true)]).unwrap(); + let child = Arc::new(Int32Array::from(vec![None, Some(1)])); + let array = UnionArray::try_new( + fields, + vec![5, 5, 5].into(), + Some(vec![1, 0, 1].into()), + vec![child], + ) + .unwrap(); + + let expected = NullBuffer::from(vec![true, false, true]); + assert_eq!(array.logical_nulls(), Some(expected)); + assert_eq!(array.logical_null_count(), 1); + } + #[test] fn test_sparse_union_logical_nulls_mask_all_nulls_skip_one() { let fields: UnionFields = [
