-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmagic_table.go
191 lines (164 loc) · 4.75 KB
/
magic_table.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
package magicsql
import (
"fmt"
"reflect"
"strings"
)
type boundField struct {
Name string
Field reflect.StructField
NoInsert bool
NoUpdate bool
}
// MagicTable represents a named database table for reading data from a single
// table into a tagged structure
type MagicTable struct {
Object interface{}
Name string
RType reflect.Type
sqlFields []*boundField
primaryKey *boundField
}
// Table registers a table name and an object's type for use in database
// operations. The returned MagicTable is pre-configured using the object's
// structure tags.
func Table(name string, obj interface{}) *MagicTable {
var mt = &MagicTable{Name: name, Object: obj}
mt.Configure(nil)
return mt
}
// Configure traverses the wrapped structure to figure out which fields map to
// database table columns and how. If conf is non-nil, that is used in place
// of struct tags. This is run if a table is created with Table(), but can be
// useful for reconfiguring a table with explicit ConfigTags.
func (t *MagicTable) Configure(conf ConfigTags) {
t.sqlFields = nil
t.RType = reflect.TypeOf(t.Object).Elem()
var rVal = reflect.ValueOf(t.Object).Elem()
for i := 0; i < t.RType.NumField(); i++ {
var sf = t.RType.Field(i)
if !rVal.Field(i).CanSet() {
continue
}
var tag string
if conf == nil {
tag = sf.Tag.Get("sql")
} else {
tag = conf[sf.Name]
}
if tag == "-" {
continue
}
var parts = strings.Split(tag, ",")
var sqlf = parts[0]
if sqlf == "" {
sqlf = toUnderscore(sf.Name)
}
var bf = &boundField{sqlf, sf, false, false}
t.sqlFields = append(t.sqlFields, bf)
if len(parts) > 1 {
for _, part := range parts[1:] {
switch part {
case "primary":
if t.primaryKey == nil {
bf.NoInsert = true
bf.NoUpdate = true
t.primaryKey = bf
}
case "readonly":
bf.NoInsert = true
bf.NoUpdate = true
case "noinsert":
bf.NoInsert = true
case "noupdate":
bf.NoUpdate = true
}
}
}
}
}
// FieldNames returns all known table field names based on the tag parsing done
// in newMagicTable
func (t *MagicTable) FieldNames() []string {
var names []string
for _, bf := range t.sqlFields {
names = append(names, bf.Name)
}
return names
}
// ScanStruct sets up a structure suitable for calling Scan to populate dest
func (t *MagicTable) ScanStruct(dest interface{}) []interface{} {
var fields = make([]interface{}, len(t.sqlFields))
var rVal = reflect.ValueOf(dest).Elem()
for i, bf := range t.sqlFields {
var vf = rVal.FieldByName(bf.Field.Name)
fields[i] = &NullableField{Value: vf.Addr().Interface()}
}
return fields
}
// InsertSQL returns the SQL string for inserting a record into this table.
// This makes the assumption that the primary key is not being set, so it isn't
// part of the fields list of values placeholder.
func (t *MagicTable) InsertSQL() string {
var fList []string
var qList []string
for _, bf := range t.sqlFields {
if bf.NoInsert {
continue
}
fList = append(fList, bf.Name)
qList = append(qList, "?")
}
var fields = strings.Join(fList, ",")
var placeholders = strings.Join(qList, ",")
return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", t.Name, fields, placeholders)
}
// InsertArgs sets up and returns an array suitable for passing to an SQL Exec
// call for doing an insert
func (t *MagicTable) InsertArgs(source interface{}) []interface{} {
var save []interface{}
var rVal = reflect.ValueOf(source).Elem()
for _, bf := range t.sqlFields {
// TODO: Make this check for empty val so we can force primary keys in explicit Insert calls
if bf.NoInsert {
continue
}
var vf = rVal.FieldByName(bf.Field.Name)
save = append(save, vf.Addr().Interface())
}
return save
}
// UpdateSQL returns the SQL string for updating a record in this table.
// Returns an empty string if there's no primary key.
func (t *MagicTable) UpdateSQL() string {
if t.primaryKey == nil {
return ""
}
var setList []string
for _, bf := range t.sqlFields {
if bf.NoUpdate {
continue
}
setList = append(setList, fmt.Sprintf("%s = ?", bf.Name))
}
var sets = strings.Join(setList, ",")
return fmt.Sprintf("UPDATE %s SET %s WHERE %s = ?", t.Name, sets, t.primaryKey.Name)
}
// UpdateArgs sets up and returns an array suitable for passing to an SQL Exec
// call for doing an update. Returns nil if there's no primary key.
func (t *MagicTable) UpdateArgs(source interface{}) []interface{} {
if t.primaryKey == nil {
return nil
}
var save []interface{}
var rVal = reflect.ValueOf(source).Elem()
for _, bf := range t.sqlFields {
if bf.NoUpdate {
continue
}
var vf = rVal.FieldByName(bf.Field.Name)
save = append(save, vf.Addr().Interface())
}
save = append(save, rVal.FieldByName(t.primaryKey.Field.Name).Addr().Interface())
return save
}