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-11644-8a9f96070f405e5a5f85a63cdc4a3d9c32a20086
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git

commit 0a2b0498c96121812686d8b626591a929c87ee06
Author: Chang Yong Lik <[email protected]>
AuthorDate: Sun Oct 18 02:26:01 2026 +0000

    fix(arrow-row): map type ids to field indexes when decoding dense union 
offsets (#11644)
    
    # Which issue does this PR close?
    
    - Closes #11599.
    
    # Rationale for this change
    
    `RowConverter::convert_rows` panicked with an index out of bounds on a
    dense union whose type ids are not below the number of fields, for
    example a single field with type id 5, although `convert_columns`
    accepted it.
    
    # What changes are included in this PR?
    
    When building the dense offsets, the decoder now looks up the field
    index with `type_id_to_field_idx`, which the same function already
    builds and uses to group the rows, instead of indexing its per-field
    counters by type id.
    
    # Are these changes tested?
    
    Yes, `test_dense_union_type_ids_not_below_field_count` round-trips a
    dense union with type ids 2 and 7. It panics 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-row/src/lib.rs | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/arrow-row/src/lib.rs b/arrow-row/src/lib.rs
index fa0d710e6c..fa2ed015c6 100644
--- a/arrow-row/src/lib.rs
+++ b/arrow-row/src/lib.rs
@@ -2475,7 +2475,7 @@ unsafe fn decode_column(
             if let Some(ref mut offsets_vec) = offsets {
                 let mut count = vec![0i32; converters.len()];
                 for type_id in &type_ids {
-                    let field_idx = *type_id as usize;
+                    let field_idx = type_id_to_field_idx[*type_id as usize];
                     offsets_vec.push(count[field_idx]);
 
                     count[field_idx] += 1;
@@ -5718,6 +5718,39 @@ mod tests {
         }
     }
 
+    #[test]
+    fn test_dense_union_type_ids_not_below_field_count() {
+        // [1, "a", 3, "b"] with type ids that are not field indexes
+        let union_fields = [
+            (2, Arc::new(Field::new("int", DataType::Int32, false))),
+            (7, Arc::new(Field::new("str", DataType::Utf8, false))),
+        ]
+        .into_iter()
+        .collect();
+
+        let union_array: ArrayRef = Arc::new(
+            UnionArray::try_new(
+                union_fields,
+                vec![2, 7, 2, 7].into(),
+                Some(vec![0, 0, 1, 1].into()),
+                vec![
+                    Arc::new(Int32Array::from(vec![1, 3])) as ArrayRef,
+                    Arc::new(StringArray::from(vec!["a", "b"])),
+                ],
+            )
+            .unwrap(),
+        );
+
+        let converter =
+            
RowConverter::new(vec![SortField::new(union_array.data_type().clone())]).unwrap();
+        let rows = converter
+            .convert_columns(std::slice::from_ref(&union_array))
+            .unwrap();
+        let back = converter.convert_rows(&rows).unwrap();
+
+        assert_eq!(back[0].to_data(), union_array.to_data());
+    }
+
     #[test]
     fn test_dense_union_with_nulls() {
         // create a dense union with Int32 (type_id = 0) and Utf8 (type_id = 1)

Reply via email to