forked from go-reform/reform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
base.go
468 lines (378 loc) · 13.3 KB
/
base.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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
package reform
import (
"database/sql"
"database/sql/driver"
"errors"
"github.com/jinzhu/gorm"
"reflect"
"strconv"
"strings"
)
var (
// ErrNoRows is returned from various methods when query produced no rows.
ErrNoRows = sql.ErrNoRows
// ErrTxDone is returned from Commit() and Rollback() TX methods when transaction is already
// committed or rolled back.
ErrTxDone = sql.ErrTxDone
// ErrNoPK is returned from various methods when primary key is required and not set.
ErrNoPK = errors.New("reform: no primary key")
)
// FieldInfo represents information about struct field.
type FieldInfo struct {
Name string // field name as defined in source file, e.g. Name
IsPK bool // is this field a primary key field
IsUnique bool // this field uses unique index in RDBMS
HasIndex bool // this field uses index in RDBMS
Type string // field type as defined in source file, e.g. string
Column string // SQL database column name from "reform:" struct field tag, e.g. name
FieldsPath []FieldInfo // A path to the field via nested structures
SQLSize int
Embedded string
StructFile string
}
func (f FieldInfo) FullName() string {
var prefix string
for _, step := range f.FieldsPath {
prefix += step.Name + "."
}
return prefix + f.Name
}
// parseStructFieldSQLTag is used by both file and runtime parsers to parse "sql" tags
func parseStructFieldSQLTag(tag string) (isUnique bool, hasIndex bool) {
parts := strings.Split(tag, ",")
for _, part := range parts {
switch part {
case "unique_index":
isUnique = true
case "index":
hasIndex = true
default:
// TODO: notify about the error
}
}
return
}
func (f *FieldInfo) ConsiderTag(imitateGorm bool, fieldName string, tag reflect.StructTag) {
var column string
var isPK bool
var embedded string
var structFile string
if imitateGorm {
column, isPK, embedded, structFile = ParseStructFieldGormTag(tag.Get("gorm"), fieldName)
} else {
column, isPK, embedded, structFile = ParseStructFieldTag(tag.Get("reform"))
}
isUnique, hasIndex := parseStructFieldSQLTag(tag.Get("sql"))
sqlSizeString := tag.Get("sql_size")
if sqlSizeString != "" {
sqlSize, err := strconv.Atoi(sqlSizeString)
if err != nil {
panic(err)
}
f.SQLSize = sqlSize
}
f.Column = column
f.IsPK = isPK
f.Embedded = embedded
f.StructFile = structFile
f.IsUnique = isUnique
f.HasIndex = hasIndex
}
// StructInfo represents information about struct.
type StructInfo struct {
Type string // struct type as defined in source file, e.g. User
SQLSchema string // SQL database schema name from magic "reform:" comment, e.g. public
SQLName string // SQL database view or table name from magic "reform:" comment, e.g. users
Fields []FieldInfo // fields info
PKFieldIndex int // index of primary key field in Fields, -1 if none
ImitateGorm bool // act like GORM (https://github.com/jinzhu/gorm)
SkipMethodOrder bool // do not create method Order()
}
// Columns returns a new slice of column names.
func (s *StructInfo) Columns() []string {
res := make([]string, len(s.Fields))
for i, f := range s.Fields {
res[i] = f.Column
}
return res
}
func (s *StructInfo) UnPointer() StructInfo {
return *s
}
func (s StructInfo) ToLog() *StructInfo {
s.SQLName += "_log"
s.Fields = append(s.Fields, []FieldInfo{
{Name: "LogAuthor", Type: "*string", Column: "log_author"},
{Name: "LogAction", Type: "string", Column: "log_action"},
{Name: "LogDate", Type: "time.Time", Column: "log_date"},
{Name: "LogComment", Type: "string", Column: "log_comment"},
}...)
return &s
}
// IsTable returns true if this object represent information for table, false for view.
func (s *StructInfo) IsTable() bool {
return s.PKFieldIndex >= 0
}
// PKField returns a primary key field, panics for views.
func (s *StructInfo) PKField() FieldInfo {
if !s.IsTable() {
panic("reform: not a table")
}
return s.Fields[s.PKFieldIndex]
}
// View represents SQL database view or table.
type View interface {
// Schema returns a schema name in SQL database.
Schema() string
// Name returns a view or table name in SQL database.
Name() string
// Columns returns a new slice of column names for that view or table in SQL database.
Columns() []string
// ColumnNameByFieldName returns the column name by a given field name
ColumnNameByFieldName(string) string
// NewStruct makes a new struct for that view or table.
NewStruct() Struct
}
// Table represents SQL database table with single-column primary key.
// It extends View.
type Table interface {
View
// NewRecord makes a new record for that table.
NewRecord() Record
// PKColumnIndex returns an index of primary key column for that table in SQL database.
PKColumnIndex() uint
// Tries to create the table if it doesn't exist
CreateTableIfNotExists(*DB) (bool, error)
}
// Struct represents a row in SQL database view or table.
type Struct interface {
// String returns a string representation of this struct or record.
String() string
// Values returns a slice of struct or record field values.
// Returned interface{} values are never untyped nils.
Values() []interface{}
// Pointers returns a slice of pointers to struct or record fields.
// Returned interface{} values are never untyped nils.
Pointers() []interface{}
// FieldPointerByName return the pointer to the field of the Struct by the field name
FieldPointerByName(string) interface{}
// FieldPointersByNames return pointers to fields of the Struct by field names
FieldPointersByNames([]string) []interface{}
// View returns View object for that struct.
View() View
}
// Record represents a row in SQL database table with single-column primary key.
type Record interface {
Struct
// Table returns Table object for that record.
Table() Table
// PKValue returns a value of primary key for that record.
// Returned interface{} value is never untyped nil.
PKValue() interface{}
// PKPointer returns a pointer to primary key field for that record.
// Returned interface{} value is never untyped nil.
PKPointer() interface{}
// HasPK returns true if record has non-zero primary key set, false otherwise.
HasPK() bool
// SetPK sets record primary key.
SetPK(pk interface{})
}
// DBTX is an interface for database connection or transaction.
// It's implemented by *sql.DB, *sql.Tx, *DB, *TX and *Querier.
type DBTX interface {
// Exec executes a query without returning any rows.
// The args are for any placeholder parameters in the query.
Exec(query string, args ...interface{}) (sql.Result, error)
// Query executes a query that returns rows, typically a SELECT.
// The args are for any placeholder parameters in the query.
Query(query string, args ...interface{}) (*sql.Rows, error)
// QueryRow executes a query that is expected to return at most one row.
// QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called.
QueryRow(query string, args ...interface{}) *sql.Row
}
type QuerierI interface {
EscapeTableName(tableName string) string
GetWhereTailForFilter(filter interface{}, columnNameByFieldName func(string) string, prefix string, imitateGorm bool) (tail string, whereTailArgs []interface{}, err error)
OperatorAndPlaceholderOfValueForSQL(valueI interface{}, placeholderCounter int) string
ValueForSQL(valueI interface{}) []interface{}
SplitConditionByPlaceholders(condition string) []string
GetDialect() Dialect
FlexSelectRows(view View, forceAnotherTable *string, forceFields []string, tail string, args ...interface{}) (*sql.Rows, error)
FlexSelectOneTo(str Struct, forceAnotherTable *string, forceFields []string, tail string, args ...interface{}) error
QualifiedView(view View) string
Insert(str Struct) error
Replace(str Struct) error
Save(record Record) error
Update(record Record) error
Delete(record Record) error
}
type ReformDBTX interface {
DBTX
QuerierI
}
// LastInsertIdMethod is a method of receiving primary key of last inserted row.
type LastInsertIdMethod int
const (
// LastInsertId is method using sql.Result.LastInsertId().
LastInsertId LastInsertIdMethod = iota
// Returning is method using "RETURNING id" SQL syntax.
Returning
// OutputInserted is method using "OUTPUT INSERTED.id" SQL syntax.
OutputInserted
)
// SelectLimitMethod is a method of limiting the number of rows in a query result.
type SelectLimitMethod int
const (
// Limit is a method using "LIMIT N" SQL syntax.
Limit SelectLimitMethod = iota
// SelectTop is a method using "SELECT TOP N" SQL syntax.
SelectTop
)
// DefaultValuesMethod is a method of inserting of row with all default values.
type DefaultValuesMethod int
const (
// DefaultValues is a method using "DEFAULT VALUES"
DefaultValues DefaultValuesMethod = iota
// EmptyLists is a method using "() VALUES ()"
EmptyLists
)
// Dialect represents differences in various SQL dialects.
type Dialect interface {
// String returns dialect name.
String() string
// Placeholder returns representation of placeholder parameter for given index,
// typically "?" or "$1".
Placeholder(index int) string
// Placeholders returns representation of placeholder parameters for given start index and count,
// typically []{"?", "?"} or []{"$1", "$2"}.
Placeholders(start, count int) []string
// QuoteIdentifier returns quoted database identifier,
// typically "identifier" or `identifier`.
QuoteIdentifier(identifier string) string
// LastInsertIdMethod returns a method of receiving primary key of last inserted row.
LastInsertIdMethod() LastInsertIdMethod
// SelectLimitMethod returns a method of limiting the number of rows in a query result.
SelectLimitMethod() SelectLimitMethod
// DefaultValuesMethod returns a method of inserting of row with all default values.
DefaultValuesMethod() DefaultValuesMethod
// ColumnDefinitionForField returns a string of column definition for a field
ColumnDefinitionForField(FieldInfo) string
// ColumnDefinitionForField returns a string of queries that should be executes after creating the field (like "CREATE INDEX" in sqlite)
ColumnDefinitionPostQueryForField(StructInfo, FieldInfo) string
}
// Stringer represents any object with method "String() string" to stringify it's value
type Stringer interface {
// Returns stringifier representation of the object
String() string
}
// DriverValuer represents any object with method "Value() (driver.Value, error)" to SQL-ify it's value
type DriverValuer interface {
// Returns SQL-fied representation of the object
Value() (driver.Value, error)
}
// AfterDBer represents any object with method "AfterDB()" to run routines required to be done after changing DB via method DB()
type AfterDBer interface {
// Runs routines required to be done after changing DB via method DB()
AfterDB()
}
// Scope represents abstract scopes
type ScopeAbstract interface {
// Get all entered parameters via method Where() (a slice of sub-slices, while every sub-slice complies to every Where() call)
GetWhere() [][]interface{}
// Get all entered parameters via method Order()
GetOrder() []string
// Get all entered parameters via method Group()
GetGroup() []string
// Get the last entered limit via method Limit()
GetLimit() int
}
type Scope interface {
ScopeAbstract
// Sets all scope-related parameters to be equal as in passed scope (as an argument)
ISetScope(Scope) Scope
GetDB() *DB
Create() error
Update() error
Save() error
Delete() error
}
type GormImitateScope interface {
ScopeAbstract
// Sets all scope-related parameters to be equal as in passed scope (as an argument)
ISetReformScope(GormImitateScope) GormImitateScope
GetReformDB() *DB
ReformCreate() error
ReformUpdate() error
ReformSave() error
ReformDelete() error
}
// parseStructFieldTag is used by both file and runtime parsers to parse "reform" tags
func ParseStructFieldTag(tag string) (sqlName string, isPK bool, embedded string, structFile string) {
parts := strings.Split(tag, ",")
if len(parts) == 0 {
return
}
sqlName = parts[0]
if len(parts) > 1 {
parts = parts[1:]
for _, part := range parts {
subParts := strings.Split(part, ":")
switch subParts[0] {
case "pk":
isPK = true
case "embedded":
embedded = subParts[1]
case "file":
structFile = subParts[1]
default:
// TODO: notify about the error
return
}
}
}
return
}
// convert structure field name to table field name using GORM rules
func toGormFieldName(fieldName string) (gormFieldName string) {
return gorm.ToDBName(fieldName)
}
// parseStructFieldGormTag is the same as parseStructFieldTag() but to parse "gorm" tags (it's for case if option "imitateGorm" is enabled)
func ParseStructFieldGormTag(tag string, fieldName string) (sqlName string, isPK bool, embedded string, structFile string) {
defer func() {
if sqlName == "" {
sqlName = toGormFieldName(fieldName)
}
}()
parts := strings.Split(tag, ";")
if len(parts) <= 1 {
isPK = fieldName == "Id"
}
if len(parts) < 1 {
return
}
/*sqlName = parts[0]
if len(parts) < 2 {
return
}*/
for _, part := range parts /*[1:]*/ {
subParts := strings.Split(part, ":")
switch subParts[0] {
case "primary_key":
isPK = true
case "column":
sqlName = subParts[1]
case "embedded":
embedded = subParts[1]
case "file":
structFile = subParts[1]
default:
// TODO: Notify about the error
}
}
return
}
// check interface
var (
_ DBTX = (*sql.DB)(nil)
_ DBTX = (*sql.Tx)(nil)
)