-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathhelpers.go
309 lines (285 loc) · 10.2 KB
/
helpers.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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
package sx
import (
"errors"
"reflect"
"strconv"
"strings"
)
// SelectQuery returns a query string of the form
//
// SELECT <columns> FROM <table>
//
// where <columns> is the list of columns defined by the struct pointed at by datatype, and <table> is the table name
// given.
func SelectQuery(table string, datatype interface{}) string {
bob := strings.Builder{}
bob.WriteString("SELECT")
var sep byte = ' '
for _, c := range matchingOf(datatype).columns {
bob.WriteByte(sep)
bob.WriteString(c.name)
sep = ','
}
bob.WriteString(" FROM ")
bob.WriteString(table)
return bob.String()
}
// SelectAliasQuery returns a query string like that of SelectQuery except that a table alias is included, e.g.
//
// SELECT <alias>.<col0>, <alias>.<col1>, ..., <alias>.<coln> FROM <table> <alias>
func SelectAliasQuery(table, alias string, datatype interface{}) string {
bob := strings.Builder{}
bob.WriteString("SELECT")
var sep byte = ' '
for _, c := range matchingOf(datatype).columns {
bob.WriteByte(sep)
bob.WriteString(alias)
bob.WriteByte('.')
bob.WriteString(c.name)
sep = ','
}
bob.WriteString(" FROM ")
bob.WriteString(table)
bob.WriteByte(' ')
bob.WriteString(alias)
return bob.String()
}
// Where returns a string of the form
//
// WHERE (<condition>) AND (<condition>) ...
//
// with a leading space.
//
// If no conditions are given, then Where returns the empty string.
func Where(conditions ...string) string {
if len(conditions) == 0 {
return ""
}
return " WHERE (" + strings.Join(conditions, ") AND (") + ")"
}
// LimitOffset returns a string of the form
//
// LIMIT <limit> OFFSET <offset>
//
// with a leading space.
//
// If either limit or offset are zero, then that part of the string is omitted. If both limit and offset are zero,
// then LimitOffset returns the empty string.
func LimitOffset(limit, offset int64) string {
x := ""
if limit != 0 {
x = " LIMIT " + strconv.FormatInt(limit, 10)
}
if offset != 0 {
x += " OFFSET " + strconv.FormatInt(offset, 10)
}
return x
}
// InsertQuery returns a query string of the form
//
// INSERT INTO <table> (<columns>) VALUES (?,?,...)
// INSERT INTO <table> (<columns>) VALUES ($1,$2,...) (numbered placeholders)
//
// where <table> is the table name given, and <columns> is the list of the columns defined by the struct pointed at by
// datatype. Struct fields tagged "readonly" are skipped.
//
// Panics if all fields are tagged "readonly".
func InsertQuery(table string, datatype interface{}) string {
columns := matchingOf(datatype).columns
bob := strings.Builder{}
bob.WriteString("INSERT INTO ")
bob.WriteString(table)
bob.WriteByte(' ')
var sep byte = '('
var n int
for _, c := range columns {
if !c.readonly {
bob.WriteByte(sep)
bob.WriteString(c.name)
sep = ','
n++
}
}
if n == 0 {
panic("sx: struct " + matchingOf(datatype).reflectType.Name() + " has no writeable fields")
}
bob.WriteString(") VALUES ")
sep = '('
for p := Placeholder(0); p < Placeholder(n); {
bob.WriteByte(sep)
bob.WriteString(p.Next())
sep = ','
}
bob.WriteByte(')')
return bob.String()
}
// UpdateQuery returns a query string and a list of values from the struct pointed at by data. This is the prefferred
// way to do updates, as it allows pointer fields in the struct and automatically skips zero values.
//
// The query string of the form
//
// UPDATE <table> SET <column>=?,<column>=?,...
// UPDATE <table> SET <column>=$2,<column>=$3,... (numbered placeholders)
//
// where <table> is the table name given, and each <column> is a column name defined by the struct pointed at by data.
//
// Note:
// - placeholder could be passed as an optional parameter ( to control numbered placeholders )
// - if not, numbering starts at $2 to allow $1 to be used in the WHERE clause ( ie. WHERE id = $1 ).
//
// The list of values contains values from the struct to match the placeholders. For pointer fields, the values
// pointed at are used.
//
// UpdateQuery takes all the writeable fields (not tagged "readonly") from the struct, looks up their values, and if
// it finds a zero value, the field is skipped. This allows the caller to set only those values that need updating.
// If it is necessary to update a field to a zero value, then a pointer field should be used. A pointer to a zero
// value will force an update, and a nil pointer will be skipped.
//
// The struct used for UpdateQuery will normally be a different struct from that used for select or insert on the
// same table. This is okay.
//
// If there are no applicable fields, Update returns ("", nil).
func UpdateQuery(table string, data interface{}, ph ...*Placeholder) (string, []interface{}) {
m := matchingOf(data)
instance := reflect.ValueOf(data).Elem()
columns := make([]string, 0)
values := make([]interface{}, 0)
// check for optional placeholder provided in the parameters
var p *Placeholder
if len(ph) > 0 {
p = ph[0]
} else {
// if not, use default one that starts from 2
var defaultPh Placeholder = 1
p = &defaultPh
}
for _, c := range m.columns {
if !c.readonly {
if val := instance.Field(c.index); !val.IsZero() {
columns = append(columns, c.name+"="+p.Next())
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
values = append(values, val.Interface())
}
}
}
if len(columns) == 0 {
return "", nil
}
return "UPDATE " + table + " SET " + strings.Join(columns, ","), values
}
// UpdateAllQuery returns a query string of the form
//
// UPDATE <table> SET <column>=?,<column>=?,...
// UPDATE <table> SET <column>=$2,<column>=$3,... (numbered placeholders)
//
// where <table> is the table name given, and each <column> is a column name defined by the struct pointed at by
// data. All writeable fields (those not tagged "readonly") are included. Fields are in the order of the struct.
//
// With numbered placeholders, numbering starts at $2. This allows $1 to be used in the WHERE clause.
//
// Use with the Values function to write to all writeable feilds.
func UpdateAllQuery(table string, data interface{}) string {
m := matchingOf(data)
columns := make([]string, 0)
var p Placeholder = 1 // start from 2
for _, c := range m.columns {
if !c.readonly {
columns = append(columns, c.name+"="+p.Next())
}
}
return "UPDATE " + table + " SET " + strings.Join(columns, ",")
}
// UpdateFieldsQuery returns a query string and a list of values for the specified fields of the struct pointed at by data.
//
// The query string is of the form
//
// UPDATE <table> SET <column>=?,<column>=?,...
// UPDATE <table> SET <column>=$2,<column>=$3,... (numbered placeholders)
//
// where <table> is the table name given, and each <column> is a column name defined by the struct pointed at by data.
//
// The list of values contains values from the struct to match the placeholders. The order matches the the order of
// fields provided by the caller.
//
// With numbered placeholders, numbering starts at $2. This allows $1 to be used in the WHERE clause.
//
// UpdateFieldsQuery panics if no field names are provided or if any of the requested fields do not exist. If it is
// necessary to validate field names, use ColumnOf.
func UpdateFieldsQuery(table string, data interface{}, fields ...string) (string, []interface{}) {
m := matchingOf(data)
instance := reflect.ValueOf(data).Elem()
columns := make([]string, 0)
values := make([]interface{}, 0)
var p Placeholder = 1 // start from 2
if len(fields) == 0 {
panic("UpdateFieldsQuery requires at least one field")
}
for _, field := range fields {
if c, ok := m.columnMap[field]; ok {
columns = append(columns, c.name+"="+p.Next())
values = append(values, instance.Field(c.index).Interface())
} else {
panic("struct " + m.reflectType.Name() + " has no usable field " + field)
}
}
return "UPDATE " + table + " SET " + strings.Join(columns, ","), values
}
// Addrs returns a slice of pointers to the fields of the struct pointed at by dest. Use for scanning rows from a
// SELECT query.
//
// Panics if dest does not point at a struct.
func Addrs(dest interface{}) []interface{} {
m := matchingOf(dest)
val := reflect.ValueOf(dest).Elem()
addrs := make([]interface{}, 0, len(m.columns))
for _, c := range m.columns {
addrs = append(addrs, val.Field(c.index).Addr().Interface())
}
return addrs
}
// Values returns a slice of values from the struct pointed at by data, excluding those from fields tagged "readonly".
// Use for providing values to an INSERT query.
//
// Panics if data does not point at a struct.
func Values(data interface{}) []interface{} {
m := matchingOf(data)
val := reflect.ValueOf(data).Elem()
values := make([]interface{}, 0, len(m.columns))
for _, c := range m.columns {
if !c.readonly {
values = append(values, val.Field(c.index).Interface())
}
}
return values
}
// ValueOf returns the value of the specified field of the struct pointed at by data. Panics if data does not
// point at a struct, or if the requested field doesn't exist.
func ValueOf(data interface{}, field string) interface{} {
// This step verifies data and field and might panic.
c := matchingOf(data).columnOf(field)
// If there is a panic, then the reflection here will not be attempted.
return reflect.ValueOf(data).Elem().Field(c.index).Interface()
}
// Columns returns the names of the database columns that correspond to the fields in the struct pointed at by
// datatype. The order of returned fields matches the order of the struct.
func Columns(datatype interface{}) []string {
return matchingOf(datatype).columnList()
}
// ColumnsWriteable returns the names of the database columns that correspond to the fields in the struct pointed at
// by datatype, excluding those tagged "readonly". The order of returned fields matches the order of the struct.
func ColumnsWriteable(datatype interface{}) []string {
return matchingOf(datatype).columnWriteableList()
}
// ColumnOf returns the name of the database column that corresponds to the specified field of the struct pointed
// at by datatype.
//
// ColumnOf returns an error if the provided field name is missing from the struct.
func ColumnOf(datatype interface{}, field string) (string, error) {
m := matchingOf(datatype)
if c, ok := m.columnMap[field]; ok {
return c.name, nil
}
return "", errors.New("struct " + m.reflectType.Name() + " has no usable field " + field)
}