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{