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 85916dd8 fix(arrow/compute): allow casting nullable struct fields to 
non-nullable when no nulls (#1363)
85916dd8 is described below

commit 85916dd841e44a7c982d73877064a08c518e9379
Author: Sankalp Thakur <[email protected]>
AuthorDate: Tue Oct 13 18:34:03 2026 +0400

    fix(arrow/compute): allow casting nullable struct fields to non-nullable 
when no nulls (#1363)
    
    ### Rationale for this change
    
    Casting a struct with a nullable field to a struct type where that field
    is non-nullable always failed with `arrow.ErrType`, even when the field
    held no nulls. The C++ implementation only rejects the cast when the
    field actually has nulls (`scalar_cast_nested.cc`).
    
    ### What changes are included in this PR?
    
    `CastStruct` no longer rejects nullable to non-nullable fields while
    matching field names. It checks each selected field's null count at cast
    time instead, and returns `arrow.ErrInvalid` with the same message as
    C++ when the field has nulls.
    
    ### Are these changes tested?
    
    Yes. `TestStructToDifferentNullabilityStruct` now covers a field with no
    nulls (cast succeeds), fields with nulls (`ErrInvalid`), and a slice
    that skips the null rows (cast succeeds).
    
    - `go test ./arrow/compute/...`: ok
    - `go vet ./arrow/compute/...` and `gofmt -l arrow/compute`: clean
    
    ### Are there any user-facing changes?
    
    Yes. These casts now succeed when the field has no nulls. When it has
    nulls, the error is now `arrow.ErrInvalid` instead of `arrow.ErrType`,
    matching C++.
    
    Fixes #133
    
    ### AI/LLM disclosure
    
    - AI coding tools (Claude Code) were used to write this change, the test
    updates and this description.
    - The change was reviewed and the commands above were run locally before
    submitting.
---
 arrow/compute/cast.go      | 12 +++++++-----
 arrow/compute/cast_test.go | 22 +++++++++++++++-------
 2 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/arrow/compute/cast.go b/arrow/compute/cast.go
index eb3e8526..e1b504c0 100644
--- a/arrow/compute/cast.go
+++ b/arrow/compute/cast.go
@@ -650,10 +650,6 @@ func CastStruct(ctx *exec.KernelCtx, batch *exec.ExecSpan, 
out *exec.ExecResult)
                inField := inType.Field(inFieldIndex)
                outField := outType.Field(outFieldIndex)
                if inField.Name == outField.Name {
-                       if inField.Nullable && !outField.Nullable {
-                               return fmt.Errorf("%w: cannot cast nullable 
field to non-nullable field: %s %s",
-                                       arrow.ErrType, inType, outType)
-                       }
                        fieldsToSelect[outFieldIndex] = inFieldIndex
                        outFieldIndex++
                }
@@ -678,7 +674,13 @@ func CastStruct(ctx *exec.KernelCtx, batch *exec.ExecSpan, 
out *exec.ExecResult)
                values = array.NewSlice(values, input.Offset, input.Len)
                defer values.Release()
 
-               opts.ToType = outType.Field(outFieldIndex).Type
+               inField, outField := inType.Field(idx), 
outType.Field(outFieldIndex)
+               if inField.Nullable && !outField.Nullable && values.NullN() > 0 
{
+                       return fmt.Errorf("%w: field '%s' of type %s has nulls. 
Can't cast to non-nullable field '%s' of type %s",
+                               arrow.ErrInvalid, inField.Name, inField.Type, 
outField.Name, outField.Type)
+               }
+
+               opts.ToType = outField.Type
                castedValues, err := CastArray(ctx.Ctx, values, &opts)
                if err != nil {
                        return err
diff --git a/arrow/compute/cast_test.go b/arrow/compute/cast_test.go
index 815c3275..f514582c 100644
--- a/arrow/compute/cast_test.go
+++ b/arrow/compute/cast_test.go
@@ -3761,8 +3761,8 @@ func (c *CastSuite) 
TestStructToDifferentNullabilityStruct() {
                dest1NonNullable := arrow.StructOf(fieldsDest1NonNullable...)
                options1NoNullable := compute.SafeCastOptions(dest1NonNullable)
                _, err = compute.CastArray(context.TODO(), srcNullable, 
options1NoNullable)
-               c.ErrorIs(err, arrow.ErrType)
-               c.ErrorContains(err, "cannot cast nullable field to 
non-nullable field")
+               c.ErrorIs(err, arrow.ErrInvalid)
+               c.ErrorContains(err, "has nulls")
 
                fieldsDest2NonNullable := []arrow.Field{
                        {Name: "a", Type: arrow.PrimitiveTypes.Int64, Nullable: 
false},
@@ -3771,17 +3771,25 @@ func (c *CastSuite) 
TestStructToDifferentNullabilityStruct() {
                dest2NonNullable := arrow.StructOf(fieldsDest2NonNullable...)
                options2NoNullable := compute.SafeCastOptions(dest2NonNullable)
                _, err = compute.CastArray(context.TODO(), srcNullable, 
options2NoNullable)
-               c.ErrorIs(err, arrow.ErrType)
-               c.ErrorContains(err, "cannot cast nullable field to 
non-nullable field")
+               c.ErrorIs(err, arrow.ErrInvalid)
+               c.ErrorContains(err, "has nulls")
 
                fieldsDest3NonNullable := []arrow.Field{
                        {Name: "c", Type: arrow.PrimitiveTypes.Int64, Nullable: 
false},
                }
                dest3NonNullable := arrow.StructOf(fieldsDest3NonNullable...)
                options3NoNullable := compute.SafeCastOptions(dest3NonNullable)
-               _, err = compute.CastArray(context.TODO(), srcNullable, 
options3NoNullable)
-               c.ErrorIs(err, arrow.ErrType)
-               c.ErrorContains(err, "cannot cast nullable field to 
non-nullable field")
+               out3, err := compute.CastArray(context.TODO(), srcNullable, 
options3NoNullable)
+               c.NoError(err) // column c has no nulls
+               defer out3.Release()
+               c.Equal(3, out3.Len())
+
+               // slice that skips the nulls in a and b succeeds
+               sliced := array.NewSlice(srcNullable, 0, 1)
+               defer sliced.Release()
+               out4, err := compute.CastArray(context.TODO(), sliced, 
options1NoNullable)
+               c.NoError(err)
+               defer out4.Release()
        })
 }
 

Reply via email to