From b7e68dbd5cbd0fe144cf5fef5a892835c5b1539e Mon Sep 17 00:00:00 2001 From: Matt Topol Date: Mon, 28 Oct 2024 16:35:13 -0400 Subject: [PATCH] fix(arrow/cdata): handle export struct with no fields (#175) Fixes #172 Includes a test that reproduced the original reported issue --- arrow/cdata/cdata_exports.go | 7 +++++++ arrow/cdata/cdata_test.go | 25 +++++++++++++++++++++++++ arrow/cdata/cdata_test_framework.go | 8 +++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/arrow/cdata/cdata_exports.go b/arrow/cdata/cdata_exports.go index d3673481..6d1e0383 100644 --- a/arrow/cdata/cdata_exports.go +++ b/arrow/cdata/cdata_exports.go @@ -401,6 +401,9 @@ func exportArray(arr arrow.Array, out *CArrowArray, outSchema *CArrowSchema) { out.children = (**CArrowArray)(unsafe.Pointer(&childPtrs[0])) case *array.Struct: out.n_children = C.int64_t(arr.NumField()) + if arr.NumField() == 0 { + return + } childPtrs := allocateArrowArrayPtrArr(arr.NumField()) children := allocateArrowArrayArr(arr.NumField()) for i := 0; i < arr.NumField(); i++ { @@ -421,6 +424,10 @@ func exportArray(arr arrow.Array, out *CArrowArray, outSchema *CArrowSchema) { exportArray(arr.Dictionary(), out.dictionary, nil) case array.Union: out.n_children = C.int64_t(arr.NumFields()) + if arr.NumFields() == 0 { + return + } + childPtrs := allocateArrowArrayPtrArr(arr.NumFields()) children := allocateArrowArrayArr(arr.NumFields()) for i := 0; i < arr.NumFields(); i++ { diff --git a/arrow/cdata/cdata_test.go b/arrow/cdata/cdata_test.go index 2a86ea62..ebd4fcfc 100644 --- a/arrow/cdata/cdata_test.go +++ b/arrow/cdata/cdata_test.go @@ -598,6 +598,28 @@ func createTestStructArr() arrow.Array { return bld.NewArray() } +func createTestEmptyStructArr() arrow.Array { + bld := array.NewStructBuilder(memory.DefaultAllocator, arrow.StructOf()) + defer bld.Release() + + bld.AppendNull() + return bld.NewArray() +} + +func createTestEmptyDenseUnionArr() arrow.Array { + bld := array.NewEmptyDenseUnionBuilder(memory.DefaultAllocator) + defer bld.Release() + + return bld.NewArray() +} + +func createTestEmptySparseUnionArr() arrow.Array { + bld := array.NewEmptySparseUnionBuilder(memory.DefaultAllocator) + defer bld.Release() + + return bld.NewArray() +} + func createTestRunEndsArr() arrow.Array { bld := array.NewRunEndEncodedBuilder(memory.DefaultAllocator, arrow.PrimitiveTypes.Int32, arrow.PrimitiveTypes.Int8) @@ -687,6 +709,9 @@ func TestNestedArrays(t *testing.T) { {"sparse union", createTestSparseUnion}, {"dense union", createTestDenseUnion}, {"run-end encoded", createTestRunEndsArr}, + {"empty struct", createTestEmptyStructArr}, + {"empty dense union", createTestEmptyDenseUnionArr}, + {"empty sparse union", createTestEmptySparseUnionArr}, } for _, tt := range tests { diff --git a/arrow/cdata/cdata_test_framework.go b/arrow/cdata/cdata_test_framework.go index c979c3fb..331e80bc 100644 --- a/arrow/cdata/cdata_test_framework.go +++ b/arrow/cdata/cdata_test_framework.go @@ -309,6 +309,9 @@ func createCArr(arr arrow.Array, alloc *mallocator.Mallocator) *CArrowArray { children = (**CArrowArray)(unsafe.Pointer(&clist[0])) nchildren += 1 case *array.Struct: + if arr.NumField() == 0 { + break + } clist := allocateChildrenPtrArr(alloc, arr.NumField()) for i := 0; i < arr.NumField(); i++ { clist[i] = createCArr(arr.Field(i), alloc) @@ -322,6 +325,9 @@ func createCArr(arr arrow.Array, alloc *mallocator.Mallocator) *CArrowArray { children = (**CArrowArray)(unsafe.Pointer(&clist[0])) nchildren += 2 case array.Union: + if arr.NumFields() == 0 { + break + } clist := allocateChildrenPtrArr(alloc, arr.NumFields()) for i := 0; i < arr.NumFields(); i++ { clist[i] = createCArr(arr.Field(i), alloc) @@ -356,7 +362,7 @@ func createCArr(arr arrow.Array, alloc *mallocator.Mallocator) *CArrowArray { tr.bufs = make([][]byte, 0, nbuffers) cbufs := allocateBufferMallocatorPtrArr(alloc, nbuffers) for i, b := range buffers[bufOffset:] { - if b != nil { + if b != nil && b.Len() > 0 { raw := alloc.Allocate(b.Len()) copy(raw, b.Bytes()) tr.bufs = append(tr.bufs, raw)