-
Notifications
You must be signed in to change notification settings - Fork 0
/
struct.go
117 lines (108 loc) · 2.63 KB
/
struct.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package table
import (
"context"
"errors"
"fmt"
"reflect"
)
// Copy Buffer into a slice of structs of type T.
// Names can be provided in `sql:"Name"` field tags. If a field should be ignored, use the `sql:"-"` tag.
// Pointer to structs or points to fields are not supported.
//
// TODO: add option to set value converter.
func BufferToStruct[T any](buf *Buffer) ([]T, error) {
list := make([]T, len(buf.Rows))
tp := reflect.TypeOf(list).Elem()
switch k := tp.Kind(); k {
default:
return nil, fmt.Errorf("invalid type kind, expected struct, got %v", k)
case reflect.Struct:
// Okay.
}
lookup := make([]int, len(buf.Columns)) // Map the buffer index to the struct index.
colMap := buf.columnNameIndex // Buffer name map[name]index.
for i := range lookup {
lookup[i] = -1
}
var missingStruct, missingBuffer []string
// The consts can be removed and the behavior locked in in the future.
// But for now,
const (
reportUnmatchedStruct = true
reportUnmatchedBuffer = false
)
// Setup the field lookup
for i := 0; i < tp.NumField(); i++ {
sf := tp.Field(i)
// Look for struct tag.
tag, ok := sf.Tag.Lookup("sql")
if ok {
if tag == "-" {
continue
}
index, ok := colMap[tag]
if ok {
lookup[index] = i
continue
}
} else {
// Attempt to match on field name.
index, ok := colMap[sf.Name]
if ok {
lookup[index] = i
continue
}
}
if reportUnmatchedStruct {
name := sf.Name
if len(tag) > 0 {
name = fmt.Sprintf("%s(tag=%s)", sf.Name, tag)
}
missingStruct = append(missingStruct, name)
}
}
if reportUnmatchedBuffer {
for bufIndex, structIndex := range lookup {
if structIndex < 0 {
name := buf.Columns[bufIndex]
if len(name) == 0 {
continue
}
missingBuffer = append(missingBuffer, name)
}
}
}
var err error
if len(missingStruct) > 0 {
err = errors.Join(err, fmt.Errorf("unused fields in struct %q", missingStruct))
}
if len(missingBuffer) > 0 {
err = errors.Join(err, fmt.Errorf("unused fields in query %q", missingBuffer))
}
if err != nil {
return nil, err
}
// Copy values to struct.
for i, row := range buf.Rows {
v := &list[i]
rv := reflect.ValueOf(v).Elem()
for bufIndex, structIndex := range lookup {
if structIndex < 0 {
continue
}
rf := rv.Field(structIndex)
fv := row.Field[bufIndex]
rfv := reflect.ValueOf(fv)
rf.Set(rfv)
}
}
return list, nil
}
// Query into a struct slice.
func QueryStruct[T any](ctx context.Context, q Queryer, text string, params ...any) ([]T, error) {
buf, err := NewBuffer(ctx, q, text, params)
if err != nil {
return nil, err
}
return BufferToStruct[T](buf)
}