/
gdb_model.go
322 lines (293 loc) · 10.9 KB
/
gdb_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
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
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/rglujing/gf.
package gdb
import (
"context"
"fmt"
"github.com/rglujing/gf/util/gconv"
"time"
"github.com/rglujing/gf/text/gregex"
"github.com/rglujing/gf/text/gstr"
)
// Model is core struct implementing the DAO for ORM.
type Model struct {
db DB // Underlying DB interface.
tx *TX // Underlying TX interface.
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
schema string // Custom database schema.
linkType int // Mark for operation on master or slave.
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
whereHolder []ModelWhereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
offset int // Offset statement for some databases grammar.
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration (< 1 for removing cache, >= 0 for saving cache).
cacheName string // Cache name for custom operation.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
}
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
type ModelHandler func(m *Model) *Model
// ChunkHandler is a function that is used in function Chunk, which handles given Result and error.
// It returns true if it wants continue chunking, or else it returns false to stop chunking.
type ChunkHandler func(result Result, err error) bool
// ModelWhereHolder is the holder for where condition preparing.
type ModelWhereHolder struct {
Operator int // Operator for this holder.
Where interface{} // Where parameter, which can commonly be type of string/map/struct.
Args []interface{} // Arguments for where parameter.
}
const (
linkTypeMaster = 1
linkTypeSlave = 2
whereHolderOperatorWhere = 1
whereHolderOperatorAnd = 2
whereHolderOperatorOr = 3
defaultFields = "*"
)
// Table is alias of Core.Model.
// See Core.Model.
// Deprecated, use Model instead.
func (c *Core) Table(tableNameQueryOrStruct ...interface{}) *Model {
return c.db.Model(tableNameQueryOrStruct...)
}
// Model creates and returns a new ORM model from given schema.
// The parameter `tableNameQueryOrStruct` can be more than one table names, and also alias name, like:
// 1. Model names:
// db.Model("user")
// db.Model("user u")
// db.Model("user, user_detail")
// db.Model("user u, user_detail ud")
// 2. Model name with alias:
// db.Model("user", "u")
// 3. Model name with sub-query:
// db.Model("? AS a, ? AS b", subQuery1, subQuery2)
func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model {
var (
tableStr string
tableName string
extraArgs []interface{}
)
// Model creation with sub-query.
if len(tableNameQueryOrStruct) > 1 {
conditionStr := gconv.String(tableNameQueryOrStruct[0])
if gstr.Contains(conditionStr, "?") {
tableStr, extraArgs = formatWhere(c.db, formatWhereInput{
Where: conditionStr,
Args: tableNameQueryOrStruct[1:],
OmitNil: false,
OmitEmpty: false,
Schema: "",
Table: "",
})
}
}
// Normal model creation.
if tableStr == "" {
tableNames := make([]string, len(tableNameQueryOrStruct))
for k, v := range tableNameQueryOrStruct {
if s, ok := v.(string); ok {
tableNames[k] = s
} else if tableName = getTableNameFromOrmTag(v); tableName != "" {
tableNames[k] = tableName
}
}
if len(tableNames) > 1 {
tableStr = fmt.Sprintf(
`%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]),
)
} else if len(tableNames) == 1 {
tableStr = c.QuotePrefixTableName(tableNames[0])
}
}
return &Model{
db: c.db,
tablesInit: tableStr,
tables: tableStr,
fields: defaultFields,
start: -1,
offset: -1,
filter: true,
extraArgs: extraArgs,
}
}
// Raw creates and returns a model based on a raw sql not a table.
// Example:
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
func (c *Core) Raw(rawSql string, args ...interface{}) *Model {
model := c.Model()
model.rawSql = rawSql
model.extraArgs = args
return model
}
// Raw creates and returns a model based on a raw sql not a table.
// Example:
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
// See Core.Raw.
func (m *Model) Raw(rawSql string, args ...interface{}) *Model {
model := m.db.Raw(rawSql, args...)
model.db = m.db
model.tx = m.tx
return model
}
func (tx *TX) Raw(rawSql string, args ...interface{}) *Model {
return tx.Model().Raw(rawSql, args...)
}
// With creates and returns an ORM model based on meta data of given object.
func (c *Core) With(objects ...interface{}) *Model {
return c.db.Model().With(objects...)
}
// Model acts like Core.Model except it operates on transaction.
// See Core.Model.
func (tx *TX) Model(tableNameQueryOrStruct ...interface{}) *Model {
model := tx.db.Model(tableNameQueryOrStruct...)
model.db = tx.db
model.tx = tx
return model
}
// With acts like Core.With except it operates on transaction.
// See Core.With.
func (tx *TX) With(object interface{}) *Model {
return tx.Model().With(object)
}
// Ctx sets the context for current operation.
func (m *Model) Ctx(ctx context.Context) *Model {
if ctx == nil {
return m
}
model := m.getModel()
model.db = model.db.Ctx(ctx)
if m.tx != nil {
model.tx = model.tx.Ctx(ctx)
}
return model
}
// GetCtx returns the context for current Model.
// It returns `context.Background()` is there's no context previously set.
func (m *Model) GetCtx() context.Context {
if m.tx != nil && m.tx.ctx != nil {
return m.tx.ctx
}
return m.db.GetCtx()
}
// As sets an alias name for current table.
func (m *Model) As(as string) *Model {
if m.tables != "" {
model := m.getModel()
split := " JOIN "
if gstr.ContainsI(model.tables, split) {
// For join table.
array := gstr.Split(model.tables, split)
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
model.tables = gstr.Join(array, split)
} else {
// For base table.
model.tables = gstr.TrimRight(model.tables) + " AS " + as
}
return model
}
return m
}
// DB sets/changes the db object for current operation.
func (m *Model) DB(db DB) *Model {
model := m.getModel()
model.db = db
return model
}
// TX sets/changes the transaction for current operation.
func (m *Model) TX(tx *TX) *Model {
model := m.getModel()
model.db = tx.db
model.tx = tx
return model
}
// Schema sets the schema for current operation.
func (m *Model) Schema(schema string) *Model {
model := m.getModel()
model.schema = schema
return model
}
// Clone creates and returns a new model which is a clone of current model.
// Note that it uses deep-copy for the clone.
func (m *Model) Clone() *Model {
newModel := (*Model)(nil)
if m.tx != nil {
newModel = m.tx.Model(m.tablesInit)
} else {
newModel = m.db.Model(m.tablesInit)
}
*newModel = *m
// Shallow copy slice attributes.
if n := len(m.extraArgs); n > 0 {
newModel.extraArgs = make([]interface{}, n)
copy(newModel.extraArgs, m.extraArgs)
}
if n := len(m.whereHolder); n > 0 {
newModel.whereHolder = make([]ModelWhereHolder, n)
copy(newModel.whereHolder, m.whereHolder)
}
if n := len(m.withArray); n > 0 {
newModel.withArray = make([]interface{}, n)
copy(newModel.withArray, m.withArray)
}
return newModel
}
// Master marks the following operation on master node.
func (m *Model) Master() *Model {
model := m.getModel()
model.linkType = linkTypeMaster
return model
}
// Slave marks the following operation on slave node.
// Note that it makes sense only if there's any slave node configured.
func (m *Model) Slave() *Model {
model := m.getModel()
model.linkType = linkTypeSlave
return model
}
// Safe marks this model safe or unsafe. If safe is true, it clones and returns a new model object
// whenever the operation done, or else it changes the attribute of current model.
func (m *Model) Safe(safe ...bool) *Model {
if len(safe) > 0 {
m.safe = safe[0]
} else {
m.safe = true
}
return m
}
// Args sets custom arguments for model operation.
func (m *Model) Args(args ...interface{}) *Model {
model := m.getModel()
model.extraArgs = append(model.extraArgs, args)
return model
}
// Handler calls each of `handlers` on current Model and returns a new Model.
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
func (m *Model) Handler(handlers ...ModelHandler) *Model {
model := m.getModel()
for _, handler := range handlers {
model = handler(model)
}
return model
}