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 1996fda0 fix(arrow/util): avoid protobuf map schema goroutines (#1082)
1996fda0 is described below
commit 1996fda09eb60eb6e1c62726c67451b53ffcf80d
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 5 20:21:35 2026 +0200
fix(arrow/util): avoid protobuf map schema goroutines (#1082)
## What changed
Build protobuf map Arrow types directly from descriptors and replace
channel-backed map entry generation with a synchronous `iter.Seq`.
## Why
Schema discovery previously started an unbuffered producer and returned
after its first result. Maps with multiple entries left that producer
blocked. Conversion failures could also stop consumption early and
strand the producer. Synchronous iteration makes early termination safe
and avoids runtime-value-dependent schema construction.
## Testing
- `go test ./arrow/util`
- Added coverage showing map schemas are independent of runtime entries
---
arrow/util/protobuf_reflect.go | 28 +++++++++++-----------------
arrow/util/protobuf_reflect_test.go | 11 +++++++++++
2 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/arrow/util/protobuf_reflect.go b/arrow/util/protobuf_reflect.go
index 1e826c3f..17fa9344 100644
--- a/arrow/util/protobuf_reflect.go
+++ b/arrow/util/protobuf_reflect.go
@@ -18,6 +18,7 @@ package util
import (
"fmt"
+ "iter"
"reflect"
"github.com/apache/arrow-go/v18/arrow"
@@ -374,12 +375,6 @@ func (pfr *ProtobufFieldReflection) asMap()
protobufMapReflection {
}
func (pmr protobufMapReflection) getDataType() arrow.DataType {
- for kvp := range pmr.generateKeyValuePairs() {
- if kvp.err != nil {
- continue
- }
- return kvp.kvp.getDataType()
- }
return protobufMapKeyValuePairReflection{
k: ProtobufFieldReflection{
parent: pmr.parent,
@@ -408,11 +403,8 @@ func (pmr protobufMapKeyValuePairReflection) getDataType()
arrow.DataType {
return arrow.MapOf(pmr.k.getDataType(), pmr.v.getDataType())
}
-func (pmr protobufMapReflection) generateKeyValuePairs() chan
protobufMapKeyValuePairResult {
- out := make(chan protobufMapKeyValuePairResult)
-
- go func() {
- defer close(out)
+func (pmr protobufMapReflection) generateKeyValuePairs()
iter.Seq[protobufMapKeyValuePairResult] {
+ return func(yield func(protobufMapKeyValuePairResult) bool) {
if !pmr.rValue.IsValid() {
kvp := protobufMapKeyValuePairReflection{
k: ProtobufFieldReflection{
@@ -426,13 +418,15 @@ func (pmr protobufMapReflection) generateKeyValuePairs()
chan protobufMapKeyValu
schemaOptions: pmr.schemaOptions,
},
}
- out <- protobufMapKeyValuePairResult{kvp: kvp}
+ yield(protobufMapKeyValuePairResult{kvp: kvp})
return
}
for _, k := range pmr.rValue.MapKeys() {
mapKey, err := getMapKey(k)
if err != nil {
- out <- protobufMapKeyValuePairResult{err: err}
+ if !yield(protobufMapKeyValuePairResult{err:
err}) {
+ return
+ }
continue
}
kvp := protobufMapKeyValuePairReflection{
@@ -451,11 +445,11 @@ func (pmr protobufMapReflection) generateKeyValuePairs()
chan protobufMapKeyValu
schemaOptions: pmr.schemaOptions,
},
}
- out <- protobufMapKeyValuePairResult{kvp: kvp}
+ if !yield(protobufMapKeyValuePairResult{kvp: kvp}) {
+ return
+ }
}
- }()
-
- return out
+ }
}
func getMapKey(v reflect.Value) (protoreflect.Value, error) {
diff --git a/arrow/util/protobuf_reflect_test.go
b/arrow/util/protobuf_reflect_test.go
index e27ba7e4..70a0e390 100644
--- a/arrow/util/protobuf_reflect_test.go
+++ b/arrow/util/protobuf_reflect_test.go
@@ -337,6 +337,17 @@ func TestGetSchema(t *testing.T) {
CheckSchema(t, pmr, want)
}
+func TestMapSchemaDoesNotDependOnValues(t *testing.T) {
+ withValues := util_message.AllTheTypesNoAny{
+ SimpleMap: map[int32]string{1: "one", 2: "two"},
+ }
+ empty := util_message.AllTheTypesNoAny{}
+
+ withValuesSchema := NewProtobufMessageReflection(&withValues).Schema()
+ emptySchema := NewProtobufMessageReflection(&empty).Schema()
+ require.True(t, withValuesSchema.Equal(emptySchema))
+}
+
func TestRecordFromProtobuf(t *testing.T) {
f := AllTheTypesFixture()