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

commit f7a72ebaa0386b93227e36f52171297391e49634
Author: timmyb32r <[email protected]>
AuthorDate: Tue Oct 27 02:30:05 2026 +0000

    Reject merging `Field`s of unions with different modes (#11827)
    
    # Which issue does this PR close?
    
    None — standalone bug fix.
    
    # Rationale for this change
    
    `Field::try_merge` accepted unions with different modes, even though
    sparse and dense unions have different physical layouts. The result
    retained the mode of `self`, silently ignoring the incompatible mode
    of the other field.
    
    # What changes are included in this PR?
    
    Return `ArrowError::SchemaError` when the union modes differ.
    
    # Are these changes tested?
    
    Added a regression test covering rejection of sparse/dense merges
    in both directions. It also verifies that unions with matching modes
    can still merge distinct variants.
    
    # Are there any significant user-facing changes?
    
    # AI assistance
    
    Codex identified the issue and generated the fix, regression tests,
    and initial PR description.
    
    Co-authored-by: Jeffrey Vo <[email protected]>
---
 arrow-schema/src/field.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 49 insertions(+), 3 deletions(-)

diff --git a/arrow-schema/src/field.rs b/arrow-schema/src/field.rs
index feebdc29cf..dba5248637 100644
--- a/arrow-schema/src/field.rs
+++ b/arrow-schema/src/field.rs
@@ -821,9 +821,15 @@ impl Field {
                     )));
                 }
             },
-            DataType::Union(nested_fields, _) => match &from.data_type {
-                DataType::Union(from_nested_fields, _) => {
-                    nested_fields.try_merge(from_nested_fields)?
+            DataType::Union(nested_fields, mode) => match &from.data_type {
+                DataType::Union(from_nested_fields, from_mode) => {
+                    if mode != from_mode {
+                        return Err(ArrowError::SchemaError(format!(
+                            "Fail to merge schema field '{}' because the from 
union mode = {:?} does not match {:?}",
+                            self.name, from_mode, mode
+                        )));
+                    }
+                    nested_fields.try_merge(from_nested_fields)?;
                 }
                 DataType::Null => {
                     self.nullable = true;
@@ -1083,6 +1089,46 @@ mod test {
         "#);
     }
 
+    #[test]
+    fn test_merge_union_modes() {
+        for mode in [UnionMode::Sparse, UnionMode::Dense] {
+            let mut field = Field::new_union(
+                "u",
+                [0],
+                [Field::new("number", DataType::Int32, false)],
+                mode,
+            );
+            let other_mode = match mode {
+                UnionMode::Sparse => UnionMode::Dense,
+                UnionMode::Dense => UnionMode::Sparse,
+            };
+            let incompatible = Field::new_union(
+                "u",
+                [0],
+                [Field::new("number", DataType::Int32, false)],
+                other_mode,
+            );
+            assert!(matches!(
+                field.try_merge(&incompatible),
+                Err(ArrowError::SchemaError(_))
+            ));
+
+            let compatible =
+                Field::new_union("u", [1], [Field::new("text", DataType::Utf8, 
false)], mode);
+            field.try_merge(&compatible).unwrap();
+            let expected = Field::new_union(
+                "u",
+                [0, 1],
+                [
+                    Field::new("number", DataType::Int32, false),
+                    Field::new("text", DataType::Utf8, false),
+                ],
+                mode,
+            );
+            assert_eq!(field, expected);
+        }
+    }
+
     #[test]
     fn test_merge_incompatible_types() {
         let mut field = Field::new("c1", DataType::Int64, false);

Reply via email to