This repository has been archived by the owner on Dec 29, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
model.go
212 lines (168 loc) · 4.97 KB
/
model.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
package database
import (
"fmt"
"reflect"
"strings"
"unicode"
)
var modelTrackingType = reflect.TypeOf(ModelTracking{})
// OnAfterPutHooker can be implemented by any model to receive a call every time
// the model is saved to the database.
type OnAfterPutHooker interface {
OnAfterPutHook() error
}
// OnBeforePutHooker can be implemented by any model to receive a call every time
// the model is retrieved from the database. When multiple models are retrieved
// (GetMulti, GetAll, ...) every single model will receive the hook individually.
type OnBeforePutHooker interface {
OnBeforePutHook() error
}
// Model should be implemented by any model that want to be serializable to SQL.
type Model interface {
// TableName returns the name of the table that should store this model. We
// recommend a simple return with the string, though it can be dynamic too
// if you really know what you are doing.
TableName() string
// Tracking returns the Tracking side helper that stores state about the model.
Tracking() *ModelTracking
}
// ModelTracking is a struct you can inherit to implement the model interface. Insert
// directly inline in your struct without pointers or names and it will provide
// you with all the functionality needed to make the struct a Model, except for
// the TableName() function that you should implement yourself.
type ModelTracking struct {
Revision int64
inserted bool
}
// Tracking returns the tracking instance of a model.
func (tracking *ModelTracking) Tracking() *ModelTracking {
return tracking
}
// StoredRevision returns the stored revision when the model was retrieved.
// It can be -1 signalling that the model is a new one.
func (tracking *ModelTracking) StoredRevision() int64 {
return tracking.Revision - 1
}
// IsInserted returns true if the model has been previously retrieved from the database.
func (tracking *ModelTracking) IsInserted() bool {
return tracking.inserted
}
// AfterGet is a hook called after a model is retrieved from the database.
func (tracking *ModelTracking) AfterGet(props []*Property) error {
tracking.inserted = true
tracking.Revision++
return nil
}
// AfterPut is a hook called after a model is updated or inserted in the database.
func (tracking *ModelTracking) AfterPut(props []*Property) error {
tracking.inserted = true
tracking.Revision++
return nil
}
// AfterDelete is a hook called after a model is deleted from the database.
func (tracking *ModelTracking) AfterDelete(props []*Property) error {
tracking.inserted = false
return nil
}
// Property represents a field of the model mapped to a database column.
type Property struct {
// Name of the column. Already escaped.
Name string
// Struct field name.
Field string
// Value of the field.
Value interface{}
// Pointer to the value of the field.
Pointer interface{}
// True if it's a primary key.
PrimaryKey bool
// Omit the column when the value is empty.
OmitEmpty bool
}
func extractModelProps(model Model) ([]*Property, error) {
v := reflect.ValueOf(model).Elem()
t := reflect.TypeOf(model).Elem()
props := []*Property{}
for i := 0; i < t.NumField(); i++ {
fv := v.Field(i)
ft := t.Field(i)
if !startsWithUppercase(ft.Name) {
continue
}
if ft.Type == modelTrackingType {
props = append(props, &Property{
Name: "`revision`",
Field: "Revision",
Value: fv.FieldByName("Revision").Interface(),
Pointer: fv.FieldByName("Revision").Addr().Interface(),
})
continue
}
prop := &Property{
Name: ft.Name,
Field: ft.Name,
Value: fv.Interface(),
Pointer: fv.Addr().Interface(),
}
tag := ft.Tag.Get("db")
if tag != "" {
parts := strings.Split(tag, ",")
if len(parts) > 2 {
return nil, fmt.Errorf("database: unknown struct tag: %s", parts[1])
}
if parts[0] != "" {
prop.Name = parts[0]
}
if len(parts) > 1 {
switch parts[1] {
case "pk":
prop.PrimaryKey = true
prop.OmitEmpty = true
case "omitempty":
prop.OmitEmpty = true
default:
return nil, fmt.Errorf("database: unknown struct tag: %s", parts[1])
}
}
}
if prop.Name == "-" {
continue
}
// Escape the name inside the SQL query. It is NOT for security.
prop.Name = fmt.Sprintf("`%s`", prop.Name)
props = append(props, prop)
}
return props, nil
}
func isZero(value interface{}) bool {
switch v := value.(type) {
case string:
return len(v) == 0
case int32:
return v == 0
case int64:
return v == 0
}
return false
}
func updatedProps(props []*Property, model Model) []*Property {
v := reflect.ValueOf(model).Elem()
var result []*Property
for _, prop := range props {
result = append(result, &Property{
Name: prop.Name,
Field: prop.Field,
Value: v.FieldByName(prop.Field).Interface(),
Pointer: v.FieldByName(prop.Field).Addr().Interface(),
PrimaryKey: prop.PrimaryKey,
OmitEmpty: prop.OmitEmpty,
})
}
return result
}
func startsWithUppercase(s string) bool {
for _, r := range s {
return unicode.IsUpper(r)
}
return false
}