-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase_crud_info.go
425 lines (381 loc) · 11.8 KB
/
base_crud_info.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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
package sqac
import (
"fmt"
"log"
"reflect"
"strconv"
"strings"
"time"
"github.com/1414C/sqac/common"
)
// CrudInfo contains information used to perform CRUD
// activities. Pre-call and post-call organization
// and formatting.
// v = Value (underlying struct of interface ptr ent)
type CrudInfo struct {
ent interface{}
log bool
mode string // "C" || "U" || "D" == create or update or delete
stype reflect.Type
flDef []common.FieldDef
tn string
fList string
vList string
fldMap map[string]interface{} // string
keyMap map[string]interface{}
incKeyName string
entValue reflect.Value
resultMap map[string]interface{}
}
// BuildComponents is used by each flavor to assemble the
// struct (entity) data for CRUD operations. There is
// some redundancy in the structure for now, as it has
// recently been migrated into BaseFlavor.
func (bf *BaseFlavor) BuildComponents(inf *CrudInfo) error {
inf.keyMap = make(map[string]interface{})
inf.fldMap = make(map[string]interface{}) // string)
inf.resultMap = make(map[string]interface{})
// http://speakmy.name/2014/09/14/modifying-interfaced-go-struct/
// get the underlying Type of the interface ptr
inf.stype = reflect.TypeOf(inf.ent).Elem()
if inf.log {
log.Println("inf.stype:", inf.stype)
}
// check that the interface type passed in was a struct
if inf.stype.Kind() != reflect.Struct {
return fmt.Errorf("only struct{} types can be passed in for table creation. got %s", inf.stype.Kind())
}
// read the tags for the struct underlying the interface ptr
var err error
inf.flDef, err = common.TagReader(inf.ent, inf.stype)
if err != nil {
log.Println("error reading model definition", err)
return err
}
if inf.log {
log.Println("inf.flDef:", inf.flDef)
}
// determine the table name as per the table creation logic
inf.tn = common.GetTableName(inf.ent)
inf.fList = "("
inf.vList = "("
// get the value that the interface ptr ent points to
// i.e. the struct holding the data for insertion
inf.entValue = reflect.ValueOf(inf.ent).Elem()
if inf.log {
log.Println("value of data in struct for insertion:", inf.entValue)
}
// what to do with sqac tags
// primary key:inc - do not fill
// primary key:"" - do nothing
// default - DEFAULT keyword for field
// nullable - if no and nil value, fill with default value for nullable type
// insQuery = "INSERT INTO depot (depot_num, region, province) VALUES (DEFAULT,'YVR','AB');"
// https: //stackoverflow.com/questions/18926303/iterate-through-the-fields-of-a-struct-in-go
// entity-type in Create CRUD call: sqac_test.Depot
// {depot_num int false [{primary_key inc} {start 90000000}]}
// {depot_bay int false [{primary_key }]}
// {create_date time.Time false [{nullable false} {default now()} {index unique}]}
// {region string false [{nullable false} {default YYC}]}
// {province string false [{nullable false} {default AB}]}
// {country string false [{nullable true} {default CA}]}
// {new_column1 string false [{nullable false}]}
// {new_column2 int64 false [{nullable false}]}
// {new_column3 float64 false [{nullable false} {default 0.0}]}
// {non_persistent_column string true []}
// iterate over the entity-struct metadata
for i, fd := range inf.flDef {
if inf.log {
log.Println(fd)
}
if fd.NoDB == true {
continue
}
bDefault := false
bPkeyInc := false
bPkey := false
bIsNull := false
// set the field attribute indicators
for _, t := range fd.SqacPairs {
switch t.Name {
case "primary_key":
if t.Value == "inc" {
bPkeyInc = true
inf.incKeyName = fd.FName //MySQL :/
} else {
bPkey = true
}
case "default":
bDefault = true
case "nullable":
// it is on the caller to know this
default:
}
}
// get the value of the current entity field
fv := inf.entValue.Field(i).Interface()
fvr := inf.entValue.Field(i)
// is the struct member a pointer?
if fvr.Kind() == reflect.Ptr {
if fvr.IsNil() {
bIsNull = true
} else {
fvr = fvr.Elem() // get the value
}
}
switch fd.UnderGoType {
case "int", "int8", "int16", "int32", "int64":
if inf.mode == "C" {
if bPkeyInc == true {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
if bDefault == true && fv == 0 ||
bDefault == true && bIsNull {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
} else {
if bPkeyInc == true || bPkey == true {
inf.keyMap[fd.FName] = fvr.Int() // reflect.ValueOf(&fvr))??
continue
}
}
// in all other cases, just use the given value making the
// assumption that the int-type field contains an int-type
inf.fList = inf.fList + fd.FName + ", "
if !bIsNull {
sv := strconv.FormatInt(fvr.Int(), 10)
inf.vList = inf.vList + sv + ", "
inf.fldMap[fd.FName] = sv
} else {
inf.vList = inf.vList + "NULL, "
inf.fldMap[fd.FName] = "NULL"
}
continue
case "uint", "uint8", "uint16", "uint32", "uint64", "rune", "byte":
if inf.mode == "C" {
if bPkeyInc == true {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
if bDefault == true && reflect.DeepEqual(fv, reflect.Zero(reflect.TypeOf(fv)).Interface()) ||
bDefault == true && bIsNull {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
} else {
if bPkeyInc == true || bPkey == true {
inf.keyMap[fd.FName] = fvr.Uint()
continue
}
}
// in all other cases, just use the given value making the
// assumption that the uint-type field contains a uint-type
inf.fList = inf.fList + fd.FName + ", "
if !bIsNull {
sv := strconv.FormatUint(fvr.Uint(), 10)
inf.vList = inf.vList + sv + ", "
inf.fldMap[fd.FName] = sv
} else {
inf.vList = inf.vList + "NULL, "
inf.fldMap[fd.FName] = "NULL"
}
continue
case "float32", "float64":
if inf.mode == "C" {
if bPkeyInc == true {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
if bDefault == true && reflect.DeepEqual(fv, reflect.Zero(reflect.TypeOf(fv)).Interface()) ||
bDefault == true && bIsNull {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
} else {
if bPkeyInc == true || bPkey == true {
inf.keyMap[fd.FName] = fvr.Float()
continue
}
}
// in all other cases, just use the given value making the
// assumption that the float-type field contains a float-type
inf.fList = inf.fList + fd.FName + ", "
if !bIsNull {
sv := strconv.FormatFloat(fvr.Float(), 'e', -1, 64)
inf.vList = inf.vList + sv + ", "
inf.fldMap[fd.FName] = sv
} else {
inf.vList = inf.vList + "NULL, "
inf.fldMap[fd.FName] = "NULL"
}
continue
case "string":
if inf.mode == "C" {
if bPkeyInc == true {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
if bDefault == true && fv == "" ||
bDefault == true && bIsNull {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
} else {
if bPkeyInc == true || bPkey == true {
inf.keyMap[fd.FName] = fvr.String()
continue
}
}
// in all other cases, just use the given value making the
// assumption that the string-type field contains a string-type
inf.fList = inf.fList + fd.FName + ", "
if !bIsNull {
inf.vList = inf.vList + "'" + fvr.String() + "', "
inf.fldMap[fd.FName] = "'" + fvr.String() + "'"
} else {
inf.vList = inf.vList + "NULL, "
inf.fldMap[fd.FName] = "NULL"
}
continue
case "bool":
if bDefault == true && fv == "" ||
bDefault == true && bIsNull {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
// in all other cases, just use the given value making the
// assumption that the string-type field contains a bool-type
inf.fList = inf.fList + fd.FName + ", "
if !bIsNull {
switch bf.GetDBDriverName() {
case "sqlite3":
i := bf.BoolToDBBool(fvr.Bool())
sv := strconv.Itoa(*i)
inf.vList = inf.vList + sv + ", "
inf.fldMap[fd.FName] = sv
case "mssql":
switch fvr.Bool() {
case true:
inf.vList = inf.vList + "1, "
inf.fldMap[fd.FName] = "1"
case false:
inf.vList = inf.vList + "0, "
inf.fldMap[fd.FName] = "0"
default:
}
default:
sv := strconv.FormatBool(fvr.Bool())
inf.vList = inf.vList + sv + ", "
inf.fldMap[fd.FName] = sv
}
} else {
inf.vList = inf.vList + "NULL, "
inf.fldMap[fd.FName] = "NULL"
}
continue
case "time.Time":
if inf.mode == "C" {
if bPkeyInc == true {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.keyMap[fd.FName] = "DEFAULT"
continue
}
// only insert with DEFAULT if a zero-value time.Time was provided or
// if a nil value was passed for a *time.Time
bZzeroTime := false
if reflect.DeepEqual(fv, reflect.Zero(reflect.TypeOf(fv)).Interface()) {
bZzeroTime = true
}
if bDefault == true && bZzeroTime || // 0001-01-01 00:00:00 +0000 UTC
bDefault == true && bIsNull {
inf.fList = inf.fList + fd.FName + ", "
inf.vList = inf.vList + "DEFAULT, "
inf.fldMap[fd.FName] = "DEFAULT"
continue
}
} else {
// deal with time keys, as they are immutable in update scenario
if bPkeyInc == true || bPkey == true {
// inf.keyMap[fd.FName] = fv.(time.Time).Format(time.RFC3339)
inf.keyMap[fd.FName] = bf.TimeToFormattedString(fv) // fv.(time.Time).Format("2006-01-02 15:04:05.999999-07:00")
continue
}
}
inf.fList = inf.fList + fd.FName + ", "
if !bIsNull {
sv := bf.TimeToFormattedString(fv)
inf.vList = inf.vList + "'" + sv + "', "
inf.fldMap[fd.FName] = "'" + sv + "'"
} else {
inf.vList = inf.vList + "NULL, "
inf.fldMap[fd.FName] = "NULL"
}
continue
default:
log.Printf("%s with go-type %s is unsupported\n", fd.FName, fd.GoType)
continue
}
}
inf.fList = strings.TrimSuffix(inf.fList, ", ")
inf.fList = inf.fList + ")"
inf.vList = strings.TrimSuffix(inf.vList, ", ")
inf.vList = inf.vList + ")"
return nil
}
// TimeToFormattedString is used to format the provided time.Time
// or *time.Time value in the string format required for the
// connected db insert or update operation. This method is called
// from within the CRUD ops for each db flavor, and could be added
// to the flavor-specific Query / Exec methods at some point.
func (bf *BaseFlavor) TimeToFormattedString(i interface{}) string {
var t time.Time
if i == nil {
panic(fmt.Errorf("nil value passed to TimeToFormattedString()"))
}
switch i.(type) {
case time.Time:
t = i.(time.Time)
case *time.Time:
iValPtr := reflect.ValueOf(i)
iVal := iValPtr.Elem()
t = iVal.Interface().(time.Time)
default:
panic(fmt.Errorf("type %v is not permitted in TimeToFormattedString()", reflect.TypeOf(i)))
}
switch bf.GetDBDriverName() {
case "postgres":
return t.Format("2006-01-02 15:04:05.999999-07:00")
case "mysql":
return t.Format("2006-01-02 15:04:05")
case "sqlite":
return t.Format("2006-01-02 15:04:05")
case "mssql":
return t.Format("2006-01-02 15:04:05.9999999")
case "hdb":
return t.Format("2006-01-02 15:04:05.9999999")
default:
// most db's will take this and convert to UTC
return t.Format("2006-01-02 15:04:05")
}
}