forked from getfider/fider
/
mapping.go
130 lines (111 loc) · 2.8 KB
/
mapping.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
package dbx
import (
"fmt"
"reflect"
"sync"
"github.com/lib/pq"
)
//RowMapper is responsible for mapping a sql.Rows into a Struct (model)
type RowMapper struct {
cache map[reflect.Type]TypeMapper
sync.RWMutex
}
//NewRowMapper creates a new instance of RowMapper
func NewRowMapper() *RowMapper {
return &RowMapper{
cache: make(map[reflect.Type]TypeMapper, 0),
}
}
//Map values from scanner (usually sql.Rows.Scan) into dest based on columns
func (m *RowMapper) Map(dest interface{}, columns []string, scanner func(dest ...interface{}) error) error {
t := reflect.TypeOf(dest)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
var (
typeMapper TypeMapper
ok bool
)
m.RLock()
typeMapper, ok = m.cache[t]
m.RUnlock()
if !ok {
typeMapper = NewTypeMapper(t)
m.Lock()
m.cache[t] = typeMapper
m.Unlock()
}
pointers := make([]interface{}, len(columns))
for i, c := range columns {
mapping := typeMapper.Fields[c]
field := reflect.ValueOf(dest).Elem()
for _, f := range mapping.FieldName {
if field.Kind() == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
field = field.Elem()
}
field = field.FieldByName(f)
}
if !field.CanAddr() {
panic(fmt.Sprintf("Field not found for column %s", c))
}
if field.Kind() == reflect.Slice && field.Type().Elem().Kind() != reflect.Uint8 {
obj := reflect.New(reflect.MakeSlice(field.Type(), 0, 0).Type()).Elem()
field.Set(obj)
pointers[i] = pq.Array(field.Addr().Interface())
} else {
pointers[i] = field.Addr().Interface()
}
}
return scanner(pointers...)
}
//TypeMapper holds information about how to map SQL ResultSet to a Struct
type TypeMapper struct {
Type reflect.Type
Fields map[string]FieldInfo
}
//NewTypeMapper creates a new instance of TypeMapper for given reflect.Type
func NewTypeMapper(t reflect.Type) TypeMapper {
all := make(map[string]FieldInfo, 0)
if t.Kind() != reflect.Struct {
return TypeMapper{
Type: t,
Fields: all,
}
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
columnName := field.Tag.Get("db")
if columnName != "" {
fieldType := field.Type
fieldKind := fieldType.Kind()
if fieldKind == reflect.Ptr {
fieldType = field.Type.Elem()
fieldKind = fieldType.Kind()
mapper := NewTypeMapper(fieldType)
for _, f := range mapper.Fields {
all[columnName+"_"+f.ColumnName] = FieldInfo{
ColumnName: columnName + "_" + f.ColumnName,
FieldName: append([]string{field.Name}, f.FieldName...),
}
}
} else {
all[columnName] = FieldInfo{
FieldName: []string{field.Name},
ColumnName: columnName,
}
}
}
}
return TypeMapper{
Type: t,
Fields: all,
}
}
//FieldInfo is a simple struct to map Column -> Field
type FieldInfo struct {
FieldName []string
ColumnName string
}