-
Notifications
You must be signed in to change notification settings - Fork 1
/
schemaless.go
207 lines (166 loc) · 5.27 KB
/
schemaless.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
package makroud
import (
"reflect"
"strings"
"github.com/pkg/errors"
"github.com/ulule/makroud/reflectx"
"github.com/ulule/makroud/snaker"
)
// Schemaless is a light version of schema for structs that are not models.
// It's a simple mapper from column name to struct field, without primary key, associations and so on...
type Schemaless struct {
rtype reflect.Type
keys map[string]SchemalessKey
}
// Type returns the reflect's type of the schema.
func (schema Schemaless) Type() reflect.Type {
return schema.rtype
}
// Name returns the type's name of the schema.
func (schema Schemaless) Name() string {
return schema.rtype.Name()
}
// Columns returns schema columns.
func (schema Schemaless) Columns() Columns {
columns := Columns{}
for _, key := range schema.keys {
columns = append(columns, key.ColumnName())
}
return columns
}
// HasColumn returns if a schema has a column or not.
func (schema Schemaless) HasColumn(column string) bool {
_, ok := schema.keys[column]
return ok
}
// Key returns the SchemalessKey instance for given column.
func (schema Schemaless) Key(column string) (SchemalessKey, bool) {
key, ok := schema.keys[column]
return key, ok
}
// ScanRow executes a scan from given row into schemaless instance.
func (schema Schemaless) ScanRow(row Row, val interface{}) error {
columns, err := row.Columns()
if err != nil {
return err
}
value := reflectx.GetIndirectValue(val)
if !reflectx.IsStruct(value) {
return errors.Wrapf(ErrStructRequired, "cannot use mapper on %T", val)
}
values, err := schema.getValues(value, columns, val)
if err != nil {
return err
}
return row.Scan(values...)
}
// ScanRows executes a scan from current row into schemaless instance.
func (schema Schemaless) ScanRows(rows Rows, val interface{}) error {
columns, err := rows.Columns()
if err != nil {
return err
}
value := reflectx.GetIndirectValue(val)
if !reflectx.IsStruct(value) {
return errors.Wrapf(ErrStructRequired, "cannot use mapper on %T", val)
}
values, err := schema.getValues(value, columns, val)
if err != nil {
return err
}
return rows.Scan(values...)
}
func (schema Schemaless) getValues(value reflect.Value, columns []string, val interface{}) ([]interface{}, error) {
values := make([]interface{}, len(columns))
missing := make([]string, 0)
for i, column := range columns {
key, ok := schema.keys[column]
if !ok {
missing = append(missing, column)
continue
}
values[i] = reflectx.GetReflectFieldByIndexes(value, key.FieldIndex())
}
if len(missing) > 0 {
return nil, errors.Wrapf(ErrSchemaColumnRequired,
"missing destination name %s in %T", strings.Join(missing, ", "), val)
}
return values, nil
}
// SchemalessKey is a light version of schema field.
// It contains the column name and the struct field information.
type SchemalessKey struct {
columnName string
fieldName string
fieldIndex []int
}
// ColumnName returns the column name for this schemaless key.
func (key SchemalessKey) ColumnName() string {
return key.columnName
}
// FieldName define the struct field name used for this schemaless key.
func (key SchemalessKey) FieldName() string {
return key.fieldName
}
// FieldIndex define the struct field index used for this schemaless key.
func (key SchemalessKey) FieldIndex() []int {
return key.fieldIndex
}
// ----------------------------------------------------------------------------
// Initializers
// ----------------------------------------------------------------------------
// GetSchemaless returns the schema information from given type that are not models.
// If no information could be gathered, it returns an error.
func GetSchemaless(driver Driver, value reflect.Type) (*Schemaless, error) {
if !driver.HasCache() {
return newSchemaless(driver, value)
}
schema := driver.GetCache().GetSchemaless(value)
if schema != nil {
return schema, nil
}
schema, err := newSchemaless(driver, value)
if err != nil {
return nil, err
}
driver.GetCache().SetSchemaless(schema)
return schema, nil
}
// newSchemaless returns a schemaless from given type, extracted by reflection.
// The returned schemaless is a mapping of a struct to columns.
// For example: Type.FieldName -> column_name
//
// If you need a mapping with a database table, please use a Schema instead of a Schemaless instance.
// You'll have better features such as primary key, foreign key, associations and so on...
func newSchemaless(driver Driver, value reflect.Type) (*Schemaless, error) {
fields, err := reflectx.GetFields(value)
if err != nil {
return nil, errors.Wrapf(err, "cannot use reflections to obtain %s fields", value.String())
}
schema := &Schemaless{
rtype: value,
keys: map[string]SchemalessKey{},
}
for _, name := range fields {
field, ok := reflectx.GetFieldByName(value, name)
if !ok {
return nil, errors.Errorf("field '%s' not found in model", name)
}
tags := GetTags(field, NewOnlyColumnTagsAnalyzerOption())
isExcluded := tags.HasKey(TagName, TagKeyIgnored) || field.PkgPath != ""
if isExcluded {
continue
}
columnName := tags.GetByKey(TagName, TagKeyColumn)
if columnName == "" {
columnName = snaker.CamelToSnake(name)
}
key := SchemalessKey{
columnName: columnName,
fieldName: field.Name,
fieldIndex: field.Index,
}
schema.keys[key.ColumnName()] = key
}
return schema, nil
}