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

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


The following commit(s) were added to refs/heads/main by this push:
     new 28ffdf09 fix(arrow/array): propagate embedded pointer nullability 
(#1038)
28ffdf09 is described below

commit 28ffdf090a9028d1a4188c3d6ed4206275fe2f09
Author: Minh Vu <[email protected]>
AuthorDate: Tue Jul 28 17:07:38 2026 +0200

    fix(arrow/array): propagate embedded pointer nullability (#1038)
    
    ## What changed
    
    Carry nullability through anonymous embedded pointer paths while
    collecting reflected struct fields.
    
    ## Why
    
    A promoted non-pointer field beneath an embedded pointer can be absent
    when that pointer is nil. The array builder already emits a null in that
    case, but schema inference marked the field non-nullable because it only
    inspected the leaf field type.
    
    This change makes the inferred schema agree with the values produced by
    the builder, including for multi-level embedded pointer paths.
    
    ## Validation
    
    `go test ./arrow/array/arreflect`
---
 arrow/array/arreflect/reflect.go                  | 17 ++++++++++-------
 arrow/array/arreflect/reflect_go_to_arrow_test.go |  2 ++
 arrow/array/arreflect/reflect_infer_test.go       | 17 +++++++++++++++++
 3 files changed, 29 insertions(+), 7 deletions(-)

diff --git a/arrow/array/arreflect/reflect.go b/arrow/array/arreflect/reflect.go
index 248350b5..54d2084f 100644
--- a/arrow/array/arreflect/reflect.go
+++ b/arrow/array/arreflect/reflect.go
@@ -143,9 +143,10 @@ func parseDecimalOpt(opts *tagOpts, token string) {
 }
 
 type bfsEntry struct {
-       t     reflect.Type
-       index []int
-       depth int
+       t        reflect.Type
+       index    []int
+       depth    int
+       nullable bool
 }
 
 type candidate struct {
@@ -214,20 +215,22 @@ func collectFieldCandidates(t reflect.Type) 
map[string][]candidate {
 
                        if sf.Anonymous && !hasTag {
                                ft := sf.Type
+                               pathNullable := entry.nullable || ft.Kind() == 
reflect.Ptr
                                for ft.Kind() == reflect.Ptr {
                                        ft = ft.Elem()
                                }
                                if ft.Kind() == reflect.Struct {
                                        queue = append(queue, bfsEntry{
-                                               t:     ft,
-                                               index: fullIndex,
-                                               depth: entry.depth + 1,
+                                               t:        ft,
+                                               index:    fullIndex,
+                                               depth:    entry.depth + 1,
+                                               nullable: pathNullable,
                                        })
                                        continue
                                }
                        }
 
-                       nullable := sf.Type.Kind() == reflect.Ptr
+                       nullable := entry.nullable || sf.Type.Kind() == 
reflect.Ptr
                        tagged := hasTag && opts.Name != ""
 
                        meta := fieldMeta{
diff --git a/arrow/array/arreflect/reflect_go_to_arrow_test.go 
b/arrow/array/arreflect/reflect_go_to_arrow_test.go
index 7f098874..4992ae77 100644
--- a/arrow/array/arreflect/reflect_go_to_arrow_test.go
+++ b/arrow/array/arreflect/reflect_go_to_arrow_test.go
@@ -312,6 +312,8 @@ func TestBuildStructArray(t *testing.T) {
                sa := arr.(*array.Struct)
                require.Equal(t, 3, sa.Len())
                require.Equal(t, 3, sa.NumField(), "expected 3 promoted fields 
(Name, City, Zip)")
+               assert.True(t, 
sa.DataType().(*arrow.StructType).Field(1).Nullable)
+               assert.True(t, 
sa.DataType().(*arrow.StructType).Field(2).Nullable)
 
                nameArr := sa.Field(0).(*array.String)
                cityArr := sa.Field(1).(*array.String)
diff --git a/arrow/array/arreflect/reflect_infer_test.go 
b/arrow/array/arreflect/reflect_infer_test.go
index 744d296f..aa94422e 100644
--- a/arrow/array/arreflect/reflect_infer_test.go
+++ b/arrow/array/arreflect/reflect_infer_test.go
@@ -154,6 +154,23 @@ func TestInferStructType(t *testing.T) {
                assert.True(t, st.Field(1).Nullable, "Label should be nullable")
        })
 
+       t.Run("fields promoted through embedded pointers are nullable", func(t 
*testing.T) {
+               type Inner struct {
+                       Value int32
+               }
+               type Middle struct {
+                       *Inner
+               }
+               type Outer struct {
+                       *Middle
+               }
+
+               st, err := inferStructType(reflect.TypeOf(Outer{}))
+               require.NoError(t, err)
+               require.Equal(t, 1, st.NumFields())
+               assert.True(t, st.Field(0).Nullable)
+       })
+
        t.Run("arrow:\"-\" tagged field is excluded", func(t *testing.T) {
                type S struct {
                        Keep   string

Reply via email to