-
Notifications
You must be signed in to change notification settings - Fork 0
/
structscanner.go
145 lines (119 loc) · 3.7 KB
/
structscanner.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
package structscanner
import (
"database/sql"
"fmt"
"reflect"
"strings"
)
// StructScanner represents a mapping from database columns to a struct type,
// and can be used to scan database rows to struct instances.
//
// Struct fields with a `db:` tag are mapped using the name specified in the tag
// as the column name. Only fields with `db:` tags are mapped.
//
// NULL values from the database are mapped to zero values on the struct.
//
// A StructScanner is not safe for concurrent use by multiple Goroutines.
type StructScanner struct {
prefix string
layout *structLayout
mappedFields []*field
mappedFieldPtrs []interface{}
}
func (s *StructScanner) columnWithoutPrefix(name string) string {
if strings.HasPrefix(name, s.prefix+".") {
return name[len(s.prefix)+1:]
}
return name
}
func (s *StructScanner) mapColumns(rows *sql.Rows) error {
if s.mappedFields != nil {
return nil
}
columns, err := rows.Columns()
if err != nil {
return err
}
s.mappedFieldPtrs = make([]interface{}, len(columns))
s.mappedFields = make([]*field, len(columns))
for i := range columns {
field := s.layout.fieldsByName[s.columnWithoutPrefix(columns[i])]
if field == nil {
panic(fmt.Sprintf("no destination field for '%s'", columns[i]))
}
s.mappedFieldPtrs[i] = reflect.New(reflect.PtrTo(field.Type)).Interface()
s.mappedFields[i] = field
}
return nil
}
// Scan populates the specified struct from a database row. Fields in the struct
// are set according to values in the row, based on the column name and the
// `db:` tag of the field. NULL values in the row result in the corresponding
// fields being set to their zero value.
//
// Columns are mapped when a row is first scanned. It is not safe to call Scan
// on a StructScanner with a query returning different columns after the first
// call.
func (s *StructScanner) Scan(rows *sql.Rows, destPtr interface{}) error {
err := s.mapColumns(rows)
if err != nil {
return err
}
err = rows.Scan(s.mappedFieldPtrs...)
if err != nil {
return err
}
s.setFields(destPtr)
return nil
}
func (s *StructScanner) setFields(destPtr interface{}) {
destValue := reflect.ValueOf(destPtr).Elem()
for i := range s.mappedFields {
instanceValue := reflect.ValueOf(s.mappedFieldPtrs[i]).Elem().Elem()
s.setNestedField(destValue, s.mappedFields[i].Indices, instanceValue)
}
}
func (s *StructScanner) setNestedField(root reflect.Value, pathIndices []int, value reflect.Value) {
destField := root
for i := range pathIndices {
if destField.Kind() == reflect.Ptr {
if destField.IsNil() {
// We’re setting a NULL - don’t keep traversing
if !value.IsValid() {
return
}
// Instantiate a zero instance for the pointer
newValue := reflect.New(destField.Type().Elem())
destField.Set(newValue)
}
destField = destField.Elem()
}
destField = destField.Field(pathIndices[i])
}
if !value.IsValid() {
destField.Set(reflect.Zero(destField.Type()))
} else if destField.Kind() == reflect.Ptr {
newValue := reflect.New(destField.Type().Elem())
newValue.Elem().Set(value)
destField.Set(newValue)
} else {
destField.Set(value)
}
}
// For returns a StructScanner suitable for scanning a struct of the type
// given by structPtr.
//
// A prefix may be specified; struct fields are mapped assuming that the columns
// from the database have the specified prefix with a dot (.) separator.
func For(structPtr interface{}, prefix string) StructScanner {
structType := reflect.TypeOf(structPtr)
cached, ok := cachedLayouts.Load(structType)
if !ok {
cached, _ = cachedLayouts.LoadOrStore(structType, newStructLayout(structType))
}
scanner := StructScanner{
prefix: prefix,
layout: cached.(*structLayout),
}
return scanner
}