You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by ze...@apache.org on 2026/07/01 15:23:54 UTC

(arrow-go) branch main updated: fix(array): struct stringer required child mask (#873)

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 d6af7dbc fix(array): struct stringer required child mask (#873)
d6af7dbc is described below

commit d6af7dbc15bb14eefca0d7207ad65f4d575a39a7
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Wed Jul 1 17:23:48 2026 +0200

    fix(array): struct stringer required child mask (#873)
    
    ### What changed
    
    Struct.String now handles child fields that do not have their own
    validity bitmap when applying the parent struct validity mask. It
    creates an all-valid bitmap for the child first, clears parent-null
    positions, and only releases an existing child bitmap when one is
    present.
    
    ### Why
    
    When a nullable struct had a required child with no child null bitmap,
    the masking path could clear bits in a zero-length slice and then
    release a nil buffer. Calling String on that shape could panic instead
    of rendering parent-null slots as null.
    
    ### Validation
    
    - go test ./arrow/array -run
    TestStructArrayStringerMasksRequiredChildWithoutNullBitmap -count=1
    - go test ./arrow/array -count=1
    -
    PARQUET_TEST_DATA=/Users/hoangvu/Code/OSS/arrow-go/parquet-testing/data
    ARROW_TEST_DATA=/Users/hoangvu/Code/OSS/arrow-go/arrow-testing/data go
    test ./... -count=1
---
 arrow/array/struct.go      | 14 +++++++++++---
 arrow/array/struct_test.go | 27 +++++++++++++++++++++++++++
 2 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/arrow/array/struct.go b/arrow/array/struct.go
index 7df6d8d4..07505de1 100644
--- a/arrow/array/struct.go
+++ b/arrow/array/struct.go
@@ -169,8 +169,14 @@ func (a *Struct) String() string {
 func (a *Struct) newStructFieldWithParentValidityMask(fieldIndex int) arrow.Array {
 	field := a.Field(fieldIndex)
 	nullBitmapBytes := field.NullBitmapBytes()
-	maskedNullBitmapBytes := make([]byte, len(nullBitmapBytes))
-	copy(maskedNullBitmapBytes, nullBitmapBytes)
+	var maskedNullBitmapBytes []byte
+	if len(nullBitmapBytes) == 0 {
+		maskedNullBitmapBytes = make([]byte, int(bitutil.BytesForBits(int64(field.Len()))))
+		bitutil.SetBitsTo(maskedNullBitmapBytes, 0, int64(field.Len()), true)
+	} else {
+		maskedNullBitmapBytes = make([]byte, len(nullBitmapBytes))
+		copy(maskedNullBitmapBytes, nullBitmapBytes)
+	}
 	for i := 0; i < field.Len(); i++ {
 		if a.IsNull(i) {
 			bitutil.ClearBit(maskedNullBitmapBytes, i)
@@ -180,7 +186,9 @@ func (a *Struct) newStructFieldWithParentValidityMask(fieldIndex int) arrow.Arra
 	defer data.Release()
 	bufs := make([]*memory.Buffer, len(data.Buffers()))
 	copy(bufs, data.buffers)
-	bufs[0].Release()
+	if bufs[0] != nil {
+		bufs[0].Release()
+	}
 	bufs[0] = memory.NewBufferBytes(maskedNullBitmapBytes)
 	data.buffers = bufs
 	maskedField := MakeFromData(data)
diff --git a/arrow/array/struct_test.go b/arrow/array/struct_test.go
index 24f522ed..216b353a 100644
--- a/arrow/array/struct_test.go
+++ b/arrow/array/struct_test.go
@@ -448,6 +448,33 @@ func TestStructArrayNullBitmap(t *testing.T) {
 	}
 }
 
+func TestStructArrayStringerMasksRequiredChildWithoutNullBitmap(t *testing.T) {
+	pool := memory.NewCheckedAllocator(memory.NewGoAllocator())
+	defer pool.AssertSize(t, 0)
+
+	childData := array.NewData(arrow.PrimitiveTypes.Int32, 3, []*memory.Buffer{
+		nil,
+		memory.NewBufferBytes(arrow.Int32Traits.CastToBytes([]int32{1, 2, 3})),
+	}, nil, 0, 0)
+	defer childData.Release()
+	child := array.MakeFromData(childData)
+	defer child.Release()
+	require.Empty(t, child.NullBitmapBytes())
+
+	fields := []arrow.Field{
+		{Name: "f1", Type: arrow.PrimitiveTypes.Int32, Nullable: false},
+	}
+	nullBitmap := memory.NewBufferBytes([]byte{0x05})
+
+	arr, err := array.NewStructArrayWithFieldsAndNulls([]arrow.Array{child}, fields, nullBitmap, 1, 0)
+	require.NoError(t, err)
+	defer arr.Release()
+
+	assert.NotPanics(t, func() {
+		assert.Equal(t, "{[1 (null) 3]}", arr.String())
+	})
+}
+
 func TestStructArrayUnmarshalJSONMissingFields(t *testing.T) {
 	pool := memory.NewGoAllocator()