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 736b184a fix(arrow/array): ignore zero-count union appends (#1026)
736b184a is described below

commit 736b184ac715b8a2042139c4f495edfbd3065efe
Author: Minh Vu <[email protected]>
AuthorDate: Mon Jul 27 19:07:29 2026 +0200

    fix(arrow/array): ignore zero-count union appends (#1026)
    
    ### Rationale for this change
    
    DenseUnionBuilder.AppendNulls(0) and AppendEmptyValues(0) still append a
    child value even though they add no union entries. That hidden mutation
    shifts the offset of the next real value and leaves inaccessible child
    data behind.
    
    ### What changes are included in this PR?
    
    * Make non-positive bulk null and empty appends no-ops.
    * Preserve the existing shared-child optimization for positive counts.
    
    ### Are these changes tested?
    
    Yes. The regression test verifies that both zero-count calls leave the
    union and child builders empty, then confirms the next real value uses
    offset zero. The full arrow/array package, assertion build, compute
    packages, and IPC package also pass.
---
 arrow/array/union.go      |  8 ++++++++
 arrow/array/union_test.go | 24 ++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/arrow/array/union.go b/arrow/array/union.go
index 58524a76..2b6c55f1 100644
--- a/arrow/array/union.go
+++ b/arrow/array/union.go
@@ -1201,6 +1201,10 @@ func (b *DenseUnionBuilder) AppendNull() {
 // for a DenseUnion this is more efficient than calling AppendNull multiple
 // times in a loop
 func (b *DenseUnionBuilder) AppendNulls(n int) {
+       if n <= 0 {
+               return
+       }
+
        // only append 1 null to the child builder, use the same offset twice
        firstChildCode := b.codes[0]
        childBuilder := b.typeIDtoBuilder[firstChildCode]
@@ -1228,6 +1232,10 @@ func (b *DenseUnionBuilder) AppendEmptyValue() {
 // at that value using the offsets n times. That makes this more efficient
 // than calling AppendEmptyValue multiple times.
 func (b *DenseUnionBuilder) AppendEmptyValues(n int) {
+       if n <= 0 {
+               return
+       }
+
        // only append 1 null to the child builder, use the same offset twice
        firstChildCode := b.codes[0]
        childBuilder := b.typeIDtoBuilder[firstChildCode]
diff --git a/arrow/array/union_test.go b/arrow/array/union_test.go
index e20f0247..24c85552 100644
--- a/arrow/array/union_test.go
+++ b/arrow/array/union_test.go
@@ -1148,6 +1148,30 @@ func TestUnions(t *testing.T) {
        suite.Run(t, new(UnionBuilderSuite))
 }
 
+func TestDenseUnionBuilderZeroBulkAppendDoesNotMutateChildren(t *testing.T) {
+       mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+       defer mem.AssertSize(t, 0)
+
+       typ := arrow.DenseUnionOf(
+               []arrow.Field{{Name: "value", Type: 
arrow.PrimitiveTypes.Int32}},
+               []arrow.UnionTypeCode{0},
+       )
+       builder := array.NewDenseUnionBuilder(mem, typ)
+       defer builder.Release()
+
+       builder.AppendNulls(0)
+       builder.AppendEmptyValues(0)
+       assert.Zero(t, builder.Len())
+       assert.Zero(t, builder.Child(0).Len())
+
+       builder.Append(0)
+       builder.Child(0).(*array.Int32Builder).Append(42)
+       result := builder.NewDenseUnionArray()
+       defer result.Release()
+       assert.EqualValues(t, 0, result.ValueOffset(0))
+       assert.EqualValues(t, 42, result.Field(0).(*array.Int32).Value(0))
+}
+
 func TestNestedUnionStructDict(t *testing.T) {
        // ARROW-18274
        dt1 := arrow.SparseUnionOf([]arrow.Field{

Reply via email to