-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathmapper.go
303 lines (261 loc) · 7.3 KB
/
mapper.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package carta
import (
"database/sql"
"errors"
"fmt"
"reflect"
"github.com/jackskj/carta/value"
)
const (
CartaTagKey string = "db"
)
// SQL Map cardinality can either be:
// Association: has-one relationship, must be nested structs in the response
// Collection: had-many relationship, repeated (slice, array) nested struct or pointer to it
type Cardinality int
const (
Unknown Cardinality = iota
Association
Collection
)
type Field struct {
Name string
Typ reflect.Type
Kind reflect.Kind
//If the field is a pointer, fields below represent the underlying type,
// these fields are here to prevent reflect.PtrTo, or reflect.elem calls when setting primatives and basic types
IsPtr bool
ElemTyp reflect.Type // if Typ is *int, elemTyp is int
ElemKind reflect.Kind // if kind is ptr and typ is *int, elem kind is int
}
type Mapper struct {
Crd Cardinality //
IsListPtr bool // true if destination is *[], false if destination is [], used only if cardinality is a collection
// Basic mapper is used for collections where underlying type is basic (any field that is able to be set, look at isBasicType for more deatils )
// for example
// type User struct {
// UserId int
// UserAddr []sql.NullString // collection submap where mapper is basic
// UserPhone []string // also basic mapper
// UserStuff *[]*string // also basic mapper
// UserBlog []*Blog // this is NOT a basic mapper
// }
// basic can only be true if cardinality is collection
IsBasic bool
Typ reflect.Type // Underlying type to be mapped
Kind reflect.Kind // Underlying Kind to be mapped
IsTypePtr bool // is the underlying type pointed to
// present columns are columns that were found to map onto a particular fild of a struct.
// those fiels must either be basic (primative, time or sql.NullXX)
PresentColumns map[string]column
// Sorted columns are present columns in consistant order,
SortedColumnIndexes []int
// when reusing the same struct multiple times, you are able to specify the colimn prefix using parent structs
// example
// type Employee struct {
// Id int
// }
// type Manager struct {
// Employee
// Employees []Employee
// }
// the following querry would correctly map if we were mapping to *[]Manager
// "select id, employees_id from employees join managers"
// employees_ is the prefix of the parent (lower case of the parent with "_")
Fields map[fieldIndex]Field
AncestorNames []string // Field.Name of ancestors
// Nested structs which correspond to any has-one has-many relationships
// int is the ith element of this struct where the submap exists
SubMaps map[fieldIndex]*Mapper
}
// Maps db rows onto the complex struct,
// Response must be a struct, pointer to a struct for our response, a slice of structs or slice of pointers to a struct
func Map(rows *sql.Rows, dst interface{}) error {
var (
mapper *Mapper
err error
rsv *resolver
)
columns, err := rows.Columns()
if err != nil {
return err
}
columnTypes, err := rows.ColumnTypes()
if err != nil {
return err
}
dstTyp := reflect.TypeOf(dst)
mapper, ok := mapperCache.loadMap(columns, dstTyp)
if !ok {
if !(isSlicePtr(dstTyp) || isStructPtr(dstTyp)) {
return fmt.Errorf("carta: cannot map rows onto %s, destination must be pointer to a slice(*[]) or pointer to a struct", dstTyp)
}
// generate new mapper
if mapper, err = newMapper(dstTyp); err != nil {
return err
}
// determine field names
if err = determineFieldsNames(mapper); err != nil {
return err
}
// Allocate columns
columnsByName := map[string]column{}
for i, columnName := range columns {
columnsByName[columnName] = column{
name: columnName,
typ: columnTypes[i],
columnIndex: i,
}
}
if err = allocateColumns(mapper, columnsByName); err != nil {
return err
}
mapperCache.storeMap(columns, dstTyp, mapper)
}
if rsv, err = mapper.loadRows(rows, columnTypes); err != nil {
return err
}
return setDst(mapper, reflect.ValueOf(dst), rsv)
}
func newMapper(t reflect.Type) (*Mapper, error) {
var (
crd Cardinality
elemTyp reflect.Type
mapper *Mapper
subMaps map[fieldIndex]*Mapper
err error
)
isListPtr := false
isBasic := false
isTypePtr := false
if isSlicePtr(t) {
crd = Collection
elemTyp = t.Elem().Elem() // *[]interface{} to intetrface{}
isListPtr = true
} else if t.Kind() == reflect.Slice {
crd = Association
crd = Collection
elemTyp = t.Elem() // []interface{} to intetrface{}
}
if crd == Collection {
isBasic = isBasicType(elemTyp)
if elemTyp.Kind() == reflect.Ptr {
elemTyp = elemTyp.Elem()
isTypePtr = true
}
}
if isStructPtr(t) {
crd = Association
elemTyp = t.Elem()
isTypePtr = true
} else if t.Kind() == reflect.Struct {
crd = Association
elemTyp = t
}
if crd == Unknown {
return nil, errors.New("carts: unknown mapping")
}
mapper = &Mapper{
Crd: crd,
IsListPtr: isListPtr,
IsBasic: isBasic,
Typ: elemTyp,
Kind: elemTyp.Kind(),
IsTypePtr: isTypePtr,
}
if subMaps, err = findSubMaps(mapper.Typ); err != nil {
return nil, err
}
mapper.SubMaps = subMaps
return mapper, nil
}
func findSubMaps(t reflect.Type) (map[fieldIndex]*Mapper, error) {
var (
subMap *Mapper
err error
)
subMaps := map[fieldIndex]*Mapper{}
if t.Kind() != reflect.Struct {
return nil, nil
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if isExported(field) && isSubMap(field.Type) {
if subMap, err = newMapper(field.Type); err != nil {
return nil, err
}
subMaps[fieldIndex(i)] = subMap
}
}
return subMaps, nil
}
func determineFieldsNames(m *Mapper) error {
var (
name string
)
fields := map[fieldIndex]Field{}
if m.IsBasic {
return nil
}
for i := 0; i < m.Typ.NumField(); i++ {
field := m.Typ.Field(i)
if isExported(field) {
if tag := nameFromTag(field.Tag); tag != "" {
name = tag
} else {
name = field.Name
}
f := Field{
Name: name,
Typ: field.Type,
Kind: field.Type.Kind(),
IsPtr: (field.Type.Kind() == reflect.Ptr),
}
if f.IsPtr {
f.ElemKind = field.Type.Elem().Kind()
f.ElemTyp = field.Type.Elem()
}
fields[fieldIndex(i)] = f
}
}
m.Fields = fields
for _, subMap := range m.SubMaps {
if err := determineFieldsNames(subMap); err != nil {
return err
}
}
return nil
}
func isExported(f reflect.StructField) bool {
return (f.PkgPath == "")
}
func nameFromTag(t reflect.StructTag) string {
return t.Get(CartaTagKey)
}
func isSubMap(t reflect.Type) bool {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return (!isBasicType(t) && (t.Kind() == reflect.Struct || t.Kind() == reflect.Slice))
}
// Basic types are any types that are intended to be set from sql row data
// Primative fields, sql.NullXXX, time.Time, proto timestamp qualify as basic
func isBasicType(t reflect.Type) bool {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if _, ok := value.BasicKinds[t.Kind()]; ok {
return true
}
if _, ok := value.BasicTypes[t]; ok {
return true
}
return false
}
// test wether the type to be set is a pointer to a struct, courtesy of BQ api
func isStructPtr(t reflect.Type) bool {
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}
func isSlicePtr(t reflect.Type) bool {
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Slice
}