Skip to content

Commit 2e35ae5

Browse files
committed
refactor(arreflect): final cleanup — extract buildEmptyTyped, delete dead types, adopt derefSliceElem
reflect.go: - Extract buildEmptyTyped from FromSlice empty-slice branch, eliminating 37-line duplication of type inference + option application - Replace validateTemporalOpt map with idiomatic switch (zero-alloc) reflect_go_to_arrow.go: - Replace 3 inline deref patterns with derefSliceElem calls reflect_arrow_to_go.go: - Extract fillFixedSizeList eliminating duplicated loop between reflect.Array and reflect.Slice cases reflect_test.go: - Delete 5 unused test struct types and TestHelpers (-58 lines dead code) reflect_public_test.go: - Add fieldValueByTag helper, replace 4 inline tag-search loops Net: -88 lines
1 parent 30cad48 commit 2e35ae5

5 files changed

Lines changed: 75 additions & 163 deletions

File tree

arrow/array/arreflect/reflect.go

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -379,15 +379,51 @@ func WithTemporal(temporal string) Option {
379379
return func(o *tagOpts) { o.Temporal = temporal }
380380
}
381381

382-
var validTemporalOpts = map[string]bool{
383-
"": true, "timestamp": true, "date32": true, "date64": true, "time32": true, "time64": true,
384-
}
385-
386382
func validateTemporalOpt(temporal string) error {
387-
if !validTemporalOpts[temporal] {
383+
switch temporal {
384+
case "", "timestamp", "date32", "date64", "time32", "time64":
385+
return nil
386+
default:
388387
return fmt.Errorf("arreflect: invalid WithTemporal value %q; valid values are date32, date64, time32, time64, timestamp: %w", temporal, ErrUnsupportedType)
389388
}
390-
return nil
389+
}
390+
391+
func buildEmptyTyped(goType reflect.Type, opts tagOpts, mem memory.Allocator) (arrow.Array, error) {
392+
dt, err := inferArrowType(goType)
393+
if err != nil {
394+
return nil, err
395+
}
396+
derefType := goType
397+
for derefType.Kind() == reflect.Ptr {
398+
derefType = derefType.Elem()
399+
}
400+
dt = applyDecimalOpts(dt, derefType, opts)
401+
dt = applyTemporalOpts(dt, derefType, opts)
402+
if opts.ListView {
403+
if derefType.Kind() != reflect.Slice || derefType == typeOfByteSlice {
404+
return nil, fmt.Errorf("arreflect: WithListView requires a slice-of-slices element type, got %s: %w", goType, ErrUnsupportedType)
405+
}
406+
innerElem := derefType.Elem()
407+
for innerElem.Kind() == reflect.Ptr {
408+
innerElem = innerElem.Elem()
409+
}
410+
innerDT, err := inferArrowType(innerElem)
411+
if err != nil {
412+
return nil, err
413+
}
414+
dt = arrow.ListViewOf(innerDT)
415+
}
416+
if opts.Dict {
417+
if err := validateDictValueType(dt); err != nil {
418+
return nil, err
419+
}
420+
dt = &arrow.DictionaryType{IndexType: arrow.PrimitiveTypes.Int32, ValueType: dt}
421+
} else if opts.REE {
422+
dt = arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32, dt)
423+
}
424+
b := array.NewBuilder(mem, dt)
425+
defer b.Release()
426+
return b.NewArray(), nil
391427
}
392428

393429
func FromSlice[T any](vals []T, mem memory.Allocator, opts ...Option) (arrow.Array, error) {
@@ -413,42 +449,7 @@ func FromSlice[T any](vals []T, mem memory.Allocator, opts ...Option) (arrow.Arr
413449
}
414450
}
415451
if len(vals) == 0 {
416-
goType := reflect.TypeFor[T]()
417-
dt, err := inferArrowType(goType)
418-
if err != nil {
419-
return nil, err
420-
}
421-
derefType := goType
422-
for derefType.Kind() == reflect.Ptr {
423-
derefType = derefType.Elem()
424-
}
425-
dt = applyDecimalOpts(dt, derefType, tOpts)
426-
dt = applyTemporalOpts(dt, derefType, tOpts)
427-
if tOpts.ListView {
428-
if derefType.Kind() != reflect.Slice || derefType == typeOfByteSlice {
429-
return nil, fmt.Errorf("arreflect: WithListView requires a slice-of-slices element type, got %s: %w", goType, ErrUnsupportedType)
430-
}
431-
innerElem := derefType.Elem()
432-
for innerElem.Kind() == reflect.Ptr {
433-
innerElem = innerElem.Elem()
434-
}
435-
innerDT, err := inferArrowType(innerElem)
436-
if err != nil {
437-
return nil, err
438-
}
439-
dt = arrow.ListViewOf(innerDT)
440-
}
441-
if tOpts.Dict {
442-
if err := validateDictValueType(dt); err != nil {
443-
return nil, err
444-
}
445-
dt = &arrow.DictionaryType{IndexType: arrow.PrimitiveTypes.Int32, ValueType: dt}
446-
} else if tOpts.REE {
447-
dt = arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32, dt)
448-
}
449-
b := array.NewBuilder(mem, dt)
450-
defer b.Release()
451-
return b.NewArray(), nil
452+
return buildEmptyTyped(reflect.TypeFor[T](), tOpts, mem)
452453
}
453454
sv := reflect.ValueOf(vals)
454455
return buildArray(sv, tOpts, mem)

arrow/array/arreflect/reflect_arrow_to_go.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,15 @@ func setMapValue(v reflect.Value, arr *array.Map, i int) error {
389389
return nil
390390
}
391391

392+
func fillFixedSizeList(dst reflect.Value, child arrow.Array, start, n int) error {
393+
for k := 0; k < n; k++ {
394+
if err := setValue(dst.Index(k), child, start+k); err != nil {
395+
return fmt.Errorf("arreflect: fixed-size list element %d: %w", k, err)
396+
}
397+
}
398+
return nil
399+
}
400+
392401
func setFixedSizeListValue(v reflect.Value, arr *array.FixedSizeList, i int) error {
393402
n := int(arr.DataType().(*arrow.FixedSizeListType).Len())
394403
child := arr.ListValues()
@@ -399,17 +408,11 @@ func setFixedSizeListValue(v reflect.Value, arr *array.FixedSizeList, i int) err
399408
if v.Len() != n {
400409
return fmt.Errorf("fixed-size list length %d does not match Go array length %d: %w", n, v.Len(), ErrTypeMismatch)
401410
}
402-
for k := 0; k < n; k++ {
403-
if err := setValue(v.Index(k), child, int(start)+k); err != nil {
404-
return fmt.Errorf("arreflect: fixed-size list element %d: %w", k, err)
405-
}
406-
}
411+
return fillFixedSizeList(v, child, int(start), n)
407412
case reflect.Slice:
408413
result := reflect.MakeSlice(v.Type(), n, n)
409-
for k := 0; k < n; k++ {
410-
if err := setValue(result.Index(k), child, int(start)+k); err != nil {
411-
return fmt.Errorf("arreflect: fixed-size list element %d: %w", k, err)
412-
}
414+
if err := fillFixedSizeList(result, child, int(start), n); err != nil {
415+
return err
413416
}
414417
v.Set(result)
415418
default:

arrow/array/arreflect/reflect_go_to_arrow.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,7 @@ func appendStructFields(sb *array.StructBuilder, v reflect.Value, fields []field
247247
}
248248

249249
func buildStructArray(vals reflect.Value, mem memory.Allocator) (arrow.Array, error) {
250-
elemType := vals.Type().Elem()
251-
isPtr := elemType.Kind() == reflect.Ptr
252-
for elemType.Kind() == reflect.Ptr {
253-
elemType = elemType.Elem()
254-
}
250+
elemType, isPtr := derefSliceElem(vals)
255251

256252
st, err := inferStructType(elemType)
257253
if err != nil {
@@ -574,11 +570,7 @@ func buildListViewArray(vals reflect.Value, mem memory.Allocator) (arrow.Array,
574570
}
575571

576572
func buildMapArray(vals reflect.Value, mem memory.Allocator) (arrow.Array, error) {
577-
mapType := vals.Type().Elem()
578-
isPtr := mapType.Kind() == reflect.Ptr
579-
for mapType.Kind() == reflect.Ptr {
580-
mapType = mapType.Elem()
581-
}
573+
mapType, isPtr := derefSliceElem(vals)
582574

583575
keyType := mapType.Key()
584576
valType := mapType.Elem()
@@ -628,11 +620,7 @@ func buildMapArray(vals reflect.Value, mem memory.Allocator) (arrow.Array, error
628620
}
629621

630622
func buildFixedSizeListArray(vals reflect.Value, mem memory.Allocator) (arrow.Array, error) {
631-
elemType := vals.Type().Elem()
632-
isPtr := elemType.Kind() == reflect.Ptr
633-
for elemType.Kind() == reflect.Ptr {
634-
elemType = elemType.Elem()
635-
}
623+
elemType, isPtr := derefSliceElem(vals)
636624

637625
if elemType.Kind() != reflect.Array {
638626
return nil, fmt.Errorf("arreflect: expected array element, got %v", elemType.Kind())

arrow/array/arreflect/reflect_public_test.go

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ import (
3030

3131
func testMem() memory.Allocator { return memory.NewGoAllocator() }
3232

33+
func fieldValueByTag(v reflect.Value, tag string) reflect.Value {
34+
for i := 0; i < v.NumField(); i++ {
35+
if v.Type().Field(i).Tag.Get("arrow") == tag {
36+
return v.Field(i)
37+
}
38+
}
39+
return reflect.Value{}
40+
}
41+
3342
func TestToGo(t *testing.T) {
3443
mem := testMem()
3544

@@ -469,16 +478,8 @@ func TestRecordAtAny(t *testing.T) {
469478
require.NoError(t, err, "RecordAtAny(0)")
470479
v := reflect.ValueOf(got)
471480
require.Equal(t, reflect.Struct, v.Kind())
472-
var nameField, scoreField reflect.Value
473-
for i := 0; i < v.NumField(); i++ {
474-
tag := v.Type().Field(i).Tag.Get("arrow")
475-
switch tag {
476-
case "name":
477-
nameField = v.Field(i)
478-
case "score":
479-
scoreField = v.Field(i)
480-
}
481-
}
481+
nameField := fieldValueByTag(v, "name")
482+
scoreField := fieldValueByTag(v, "score")
482483
require.True(t, nameField.IsValid(), "name field not found")
483484
require.True(t, scoreField.IsValid(), "score field not found")
484485
assert.Equal(t, "alice", nameField.String())
@@ -502,12 +503,7 @@ func TestRecordToAnySlice(t *testing.T) {
502503
for i, row := range got {
503504
v := reflect.ValueOf(row)
504505
require.Equal(t, reflect.Struct, v.Kind(), "row %d", i)
505-
var nameField reflect.Value
506-
for fi := 0; fi < v.NumField(); fi++ {
507-
if v.Type().Field(fi).Tag.Get("arrow") == "name" {
508-
nameField = v.Field(fi)
509-
}
510-
}
506+
nameField := fieldValueByTag(v, "name")
511507
assert.Equal(t, rows[i].Name, nameField.String(), "row %d name", i)
512508
}
513509
}
@@ -534,17 +530,8 @@ func TestAtAnyComposite(t *testing.T) {
534530
v := reflect.ValueOf(got)
535531
require.Equal(t, reflect.Struct, v.Kind())
536532

537-
vt := v.Type()
538-
var idField, nameField reflect.Value
539-
for i := 0; i < v.NumField(); i++ {
540-
tag := vt.Field(i).Tag.Get("arrow")
541-
switch tag {
542-
case "id":
543-
idField = v.Field(i)
544-
case "name":
545-
nameField = v.Field(i)
546-
}
547-
}
533+
idField := fieldValueByTag(v, "id")
534+
nameField := fieldValueByTag(v, "name")
548535
require.True(t, idField.IsValid(), "id field not found")
549536
require.True(t, nameField.IsValid(), "name field not found")
550537
assert.Equal(t, int64(99), idField.Int())
@@ -640,17 +627,9 @@ func TestToAnySliceStructArray(t *testing.T) {
640627
require.Equal(t, reflect.Struct, v.Kind(), "row %d", i)
641628
require.Equal(t, 3, v.NumField(), "row %d", i)
642629

643-
var id, label, score reflect.Value
644-
for fi := 0; fi < v.NumField(); fi++ {
645-
switch v.Type().Field(fi).Tag.Get("arrow") {
646-
case "id":
647-
id = v.Field(fi)
648-
case "label":
649-
label = v.Field(fi)
650-
case "score":
651-
score = v.Field(fi)
652-
}
653-
}
630+
id := fieldValueByTag(v, "id")
631+
label := fieldValueByTag(v, "label")
632+
score := fieldValueByTag(v, "score")
654633
require.True(t, id.IsValid(), "row %d: id field not found", i)
655634
require.True(t, label.IsValid(), "row %d: label field not found", i)
656635
require.True(t, score.IsValid(), "row %d: score field not found", i)

arrow/array/arreflect/reflect_test.go

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -197,62 +197,3 @@ func TestCachedStructFields(t *testing.T) {
197197
assert.Equal(t, "X", fields1[0].Name)
198198
assert.Equal(t, "Y", fields1[1].Name)
199199
}
200-
201-
// ── shared test types used across reflect test files ──────────────────────────
202-
203-
type testPrimitive struct {
204-
I8 int8
205-
I16 int16
206-
I32 int32
207-
I64 int64
208-
U8 uint8
209-
U16 uint16
210-
U32 uint32
211-
U64 uint64
212-
F32 float32
213-
F64 float64
214-
B bool
215-
S string
216-
Blob []byte
217-
}
218-
219-
type testNested struct {
220-
Name string
221-
Scores []float64
222-
Tags map[string]string
223-
Address struct {
224-
City string
225-
Zip int32
226-
}
227-
}
228-
229-
type testNullable struct {
230-
Required string
231-
Optional *string
232-
MaybeInt *int32
233-
}
234-
235-
type testEmbedded struct {
236-
ID string
237-
testEmbeddedInner
238-
}
239-
240-
type testEmbeddedInner struct { //nolint:unused
241-
City string
242-
Code int32
243-
}
244-
245-
type testTagged struct {
246-
UserName string `arrow:"user_name"`
247-
Score float64 `arrow:"score"`
248-
Hidden string `arrow:"-"`
249-
}
250-
251-
func TestHelpers(t *testing.T) {
252-
// Verify shared test types are usable
253-
_ = testPrimitive{I8: 1, I32: 2, S: "hi"}
254-
_ = testNested{Name: "n", Scores: []float64{1.0}}
255-
_ = testNullable{Required: "r"}
256-
_ = testTagged{UserName: "u", Score: 3.14}
257-
_ = testEmbedded{ID: "id"}
258-
}

0 commit comments

Comments
 (0)