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 <[email protected]>
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()