forked from go-ozzo/ozzo-dbx
-
Notifications
You must be signed in to change notification settings - Fork 31
/
builder.go
402 lines (352 loc) · 16.3 KB
/
builder.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
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package dbx
import (
"errors"
"fmt"
"sort"
"strings"
)
// Builder supports building SQL statements in a DB-agnostic way.
// Builder mainly provides two sets of query building methods: those building SELECT statements
// and those manipulating DB data or schema (e.g. INSERT statements, CREATE TABLE statements).
type Builder interface {
// NewQuery creates a new Query object with the given SQL statement.
// The SQL statement may contain parameter placeholders which can be bound with actual parameter
// values before the statement is executed.
NewQuery(string) *Query
// Select returns a new SelectQuery object that can be used to build a SELECT statement.
// The parameters to this method should be the list column names to be selected.
// A column name may have an optional alias name. For example, Select("id", "my_name AS name").
Select(...string) *SelectQuery
// ModelQuery returns a new ModelQuery object that can be used to perform model insertion, update, and deletion.
// The parameter to this method should be a pointer to the model struct that needs to be inserted, updated, or deleted.
Model(interface{}) *ModelQuery
// GeneratePlaceholder generates an anonymous parameter placeholder with the given parameter ID.
GeneratePlaceholder(int) string
// Quote quotes a string so that it can be embedded in a SQL statement as a string value.
Quote(string) string
// QuoteSimpleTableName quotes a simple table name.
// A simple table name does not contain any schema prefix.
QuoteSimpleTableName(string) string
// QuoteSimpleColumnName quotes a simple column name.
// A simple column name does not contain any table prefix.
QuoteSimpleColumnName(string) string
// QueryBuilder returns the query builder supporting the current DB.
QueryBuilder() QueryBuilder
// Insert creates a Query that represents an INSERT SQL statement.
// The keys of cols are the column names, while the values of cols are the corresponding column
// values to be inserted.
Insert(table string, cols Params) *Query
// Upsert creates a Query that represents an UPSERT SQL statement.
// Upsert inserts a row into the table if the primary key or unique index is not found.
// Otherwise it will update the row with the new values.
// The keys of cols are the column names, while the values of cols are the corresponding column
// values to be inserted.
Upsert(table string, cols Params, constraints ...string) *Query
// Update creates a Query that represents an UPDATE SQL statement.
// The keys of cols are the column names, while the values of cols are the corresponding new column
// values. If the "where" expression is nil, the UPDATE SQL statement will have no WHERE clause
// (be careful in this case as the SQL statement will update ALL rows in the table).
Update(table string, cols Params, where Expression) *Query
// Delete creates a Query that represents a DELETE SQL statement.
// If the "where" expression is nil, the DELETE SQL statement will have no WHERE clause
// (be careful in this case as the SQL statement will delete ALL rows in the table).
Delete(table string, where Expression) *Query
// CreateTable creates a Query that represents a CREATE TABLE SQL statement.
// The keys of cols are the column names, while the values of cols are the corresponding column types.
// The optional "options" parameters will be appended to the generated SQL statement.
CreateTable(table string, cols map[string]string, options ...string) *Query
// RenameTable creates a Query that can be used to rename a table.
RenameTable(oldName, newName string) *Query
// DropTable creates a Query that can be used to drop a table.
DropTable(table string) *Query
// TruncateTable creates a Query that can be used to truncate a table.
TruncateTable(table string) *Query
// AddColumn creates a Query that can be used to add a column to a table.
AddColumn(table, col, typ string) *Query
// DropColumn creates a Query that can be used to drop a column from a table.
DropColumn(table, col string) *Query
// RenameColumn creates a Query that can be used to rename a column in a table.
RenameColumn(table, oldName, newName string) *Query
// AlterColumn creates a Query that can be used to change the definition of a table column.
AlterColumn(table, col, typ string) *Query
// AddPrimaryKey creates a Query that can be used to specify primary key(s) for a table.
// The "name" parameter specifies the name of the primary key constraint.
AddPrimaryKey(table, name string, cols ...string) *Query
// DropPrimaryKey creates a Query that can be used to remove the named primary key constraint from a table.
DropPrimaryKey(table, name string) *Query
// AddForeignKey creates a Query that can be used to add a foreign key constraint to a table.
// The length of cols and refCols must be the same as they refer to the primary and referential columns.
// The optional "options" parameters will be appended to the SQL statement. They can be used to
// specify options such as "ON DELETE CASCADE".
AddForeignKey(table, name string, cols, refCols []string, refTable string, options ...string) *Query
// DropForeignKey creates a Query that can be used to remove the named foreign key constraint from a table.
DropForeignKey(table, name string) *Query
// CreateIndex creates a Query that can be used to create an index for a table.
CreateIndex(table, name string, cols ...string) *Query
// CreateUniqueIndex creates a Query that can be used to create a unique index for a table.
CreateUniqueIndex(table, name string, cols ...string) *Query
// DropIndex creates a Query that can be used to remove the named index from a table.
DropIndex(table, name string) *Query
}
// BaseBuilder provides a basic implementation of the Builder interface.
type BaseBuilder struct {
db *DB
executor Executor
}
// NewBaseBuilder creates a new BaseBuilder instance.
func NewBaseBuilder(db *DB, executor Executor) *BaseBuilder {
return &BaseBuilder{db, executor}
}
// DB returns the DB instance that this builder is associated with.
func (b *BaseBuilder) DB() *DB {
return b.db
}
// Executor returns the executor object (a DB instance or a transaction) for executing SQL statements.
func (b *BaseBuilder) Executor() Executor {
return b.executor
}
// NewQuery creates a new Query object with the given SQL statement.
// The SQL statement may contain parameter placeholders which can be bound with actual parameter
// values before the statement is executed.
func (b *BaseBuilder) NewQuery(sql string) *Query {
return NewQuery(b.db, b.executor, sql)
}
// GeneratePlaceholder generates an anonymous parameter placeholder with the given parameter ID.
func (b *BaseBuilder) GeneratePlaceholder(int) string {
return "?"
}
// Quote quotes a string so that it can be embedded in a SQL statement as a string value.
func (b *BaseBuilder) Quote(s string) string {
return "'" + strings.Replace(s, "'", "''", -1) + "'"
}
// QuoteSimpleTableName quotes a simple table name.
// A simple table name does not contain any schema prefix.
func (b *BaseBuilder) QuoteSimpleTableName(s string) string {
if strings.Contains(s, `"`) {
return s
}
return `"` + s + `"`
}
// QuoteSimpleColumnName quotes a simple column name.
// A simple column name does not contain any table prefix.
func (b *BaseBuilder) QuoteSimpleColumnName(s string) string {
if strings.Contains(s, `"`) || s == "*" {
return s
}
return `"` + s + `"`
}
// Insert creates a Query that represents an INSERT SQL statement.
// The keys of cols are the column names, while the values of cols are the corresponding column
// values to be inserted.
func (b *BaseBuilder) Insert(table string, cols Params) *Query {
names := make([]string, 0, len(cols))
for name := range cols {
names = append(names, name)
}
sort.Strings(names)
params := Params{}
columns := make([]string, 0, len(names))
values := make([]string, 0, len(names))
for _, name := range names {
columns = append(columns, b.db.QuoteColumnName(name))
value := cols[name]
if e, ok := value.(Expression); ok {
values = append(values, e.Build(b.db, params))
} else {
values = append(values, fmt.Sprintf("{:p%v}", len(params)))
params[fmt.Sprintf("p%v", len(params))] = value
}
}
var sql string
if len(names) == 0 {
sql = fmt.Sprintf("INSERT INTO %v DEFAULT VALUES", b.db.QuoteTableName(table))
} else {
sql = fmt.Sprintf("INSERT INTO %v (%v) VALUES (%v)",
b.db.QuoteTableName(table),
strings.Join(columns, ", "),
strings.Join(values, ", "),
)
}
return b.NewQuery(sql).Bind(params)
}
// Upsert creates a Query that represents an UPSERT SQL statement.
// Upsert inserts a row into the table if the primary key or unique index is not found.
// Otherwise it will update the row with the new values.
// The keys of cols are the column names, while the values of cols are the corresponding column
// values to be inserted.
func (b *BaseBuilder) Upsert(table string, cols Params, constraints ...string) *Query {
q := b.NewQuery("")
q.LastError = errors.New("Upsert is not supported")
return q
}
// Update creates a Query that represents an UPDATE SQL statement.
// The keys of cols are the column names, while the values of cols are the corresponding new column
// values. If the "where" expression is nil, the UPDATE SQL statement will have no WHERE clause
// (be careful in this case as the SQL statement will update ALL rows in the table).
func (b *BaseBuilder) Update(table string, cols Params, where Expression) *Query {
names := make([]string, 0, len(cols))
for name := range cols {
names = append(names, name)
}
sort.Strings(names)
params := Params{}
lines := make([]string, 0, len(names))
for _, name := range names {
value := cols[name]
name = b.db.QuoteColumnName(name)
if e, ok := value.(Expression); ok {
lines = append(lines, name+"="+e.Build(b.db, params))
} else {
lines = append(lines, fmt.Sprintf("%v={:p%v}", name, len(params)))
params[fmt.Sprintf("p%v", len(params))] = value
}
}
sql := fmt.Sprintf("UPDATE %v SET %v", b.db.QuoteTableName(table), strings.Join(lines, ", "))
if where != nil {
w := where.Build(b.db, params)
if w != "" {
sql += " WHERE " + w
}
}
return b.NewQuery(sql).Bind(params)
}
// Delete creates a Query that represents a DELETE SQL statement.
// If the "where" expression is nil, the DELETE SQL statement will have no WHERE clause
// (be careful in this case as the SQL statement will delete ALL rows in the table).
func (b *BaseBuilder) Delete(table string, where Expression) *Query {
sql := "DELETE FROM " + b.db.QuoteTableName(table)
params := Params{}
if where != nil {
w := where.Build(b.db, params)
if w != "" {
sql += " WHERE " + w
}
}
return b.NewQuery(sql).Bind(params)
}
// CreateTable creates a Query that represents a CREATE TABLE SQL statement.
// The keys of cols are the column names, while the values of cols are the corresponding column types.
// The optional "options" parameters will be appended to the generated SQL statement.
func (b *BaseBuilder) CreateTable(table string, cols map[string]string, options ...string) *Query {
names := []string{}
for name := range cols {
names = append(names, name)
}
sort.Strings(names)
columns := []string{}
for _, name := range names {
columns = append(columns, b.db.QuoteColumnName(name)+" "+cols[name])
}
sql := fmt.Sprintf("CREATE TABLE %v (%v)", b.db.QuoteTableName(table), strings.Join(columns, ", "))
for _, opt := range options {
sql += " " + opt
}
return b.NewQuery(sql)
}
// RenameTable creates a Query that can be used to rename a table.
func (b *BaseBuilder) RenameTable(oldName, newName string) *Query {
sql := fmt.Sprintf("RENAME TABLE %v TO %v", b.db.QuoteTableName(oldName), b.db.QuoteTableName(newName))
return b.NewQuery(sql)
}
// DropTable creates a Query that can be used to drop a table.
func (b *BaseBuilder) DropTable(table string) *Query {
sql := "DROP TABLE " + b.db.QuoteTableName(table)
return b.NewQuery(sql)
}
// TruncateTable creates a Query that can be used to truncate a table.
func (b *BaseBuilder) TruncateTable(table string) *Query {
sql := "TRUNCATE TABLE " + b.db.QuoteTableName(table)
return b.NewQuery(sql)
}
// AddColumn creates a Query that can be used to add a column to a table.
func (b *BaseBuilder) AddColumn(table, col, typ string) *Query {
sql := fmt.Sprintf("ALTER TABLE %v ADD %v %v", b.db.QuoteTableName(table), b.db.QuoteColumnName(col), typ)
return b.NewQuery(sql)
}
// DropColumn creates a Query that can be used to drop a column from a table.
func (b *BaseBuilder) DropColumn(table, col string) *Query {
sql := fmt.Sprintf("ALTER TABLE %v DROP COLUMN %v", b.db.QuoteTableName(table), b.db.QuoteColumnName(col))
return b.NewQuery(sql)
}
// RenameColumn creates a Query that can be used to rename a column in a table.
func (b *BaseBuilder) RenameColumn(table, oldName, newName string) *Query {
sql := fmt.Sprintf("ALTER TABLE %v RENAME COLUMN %v TO %v", b.db.QuoteTableName(table), b.db.QuoteColumnName(oldName), b.db.QuoteColumnName(newName))
return b.NewQuery(sql)
}
// AlterColumn creates a Query that can be used to change the definition of a table column.
func (b *BaseBuilder) AlterColumn(table, col, typ string) *Query {
col = b.db.QuoteColumnName(col)
sql := fmt.Sprintf("ALTER TABLE %v CHANGE %v %v %v", b.db.QuoteTableName(table), col, col, typ)
return b.NewQuery(sql)
}
// AddPrimaryKey creates a Query that can be used to specify primary key(s) for a table.
// The "name" parameter specifies the name of the primary key constraint.
func (b *BaseBuilder) AddPrimaryKey(table, name string, cols ...string) *Query {
sql := fmt.Sprintf("ALTER TABLE %v ADD CONSTRAINT %v PRIMARY KEY (%v)",
b.db.QuoteTableName(table),
b.db.QuoteColumnName(name),
b.quoteColumns(cols))
return b.NewQuery(sql)
}
// DropPrimaryKey creates a Query that can be used to remove the named primary key constraint from a table.
func (b *BaseBuilder) DropPrimaryKey(table, name string) *Query {
sql := fmt.Sprintf("ALTER TABLE %v DROP CONSTRAINT %v", b.db.QuoteTableName(table), b.db.QuoteColumnName(name))
return b.NewQuery(sql)
}
// AddForeignKey creates a Query that can be used to add a foreign key constraint to a table.
// The length of cols and refCols must be the same as they refer to the primary and referential columns.
// The optional "options" parameters will be appended to the SQL statement. They can be used to
// specify options such as "ON DELETE CASCADE".
func (b *BaseBuilder) AddForeignKey(table, name string, cols, refCols []string, refTable string, options ...string) *Query {
sql := fmt.Sprintf("ALTER TABLE %v ADD CONSTRAINT %v FOREIGN KEY (%v) REFERENCES %v (%v)",
b.db.QuoteTableName(table),
b.db.QuoteColumnName(name),
b.quoteColumns(cols),
b.db.QuoteTableName(refTable),
b.quoteColumns(refCols))
for _, opt := range options {
sql += " " + opt
}
return b.NewQuery(sql)
}
// DropForeignKey creates a Query that can be used to remove the named foreign key constraint from a table.
func (b *BaseBuilder) DropForeignKey(table, name string) *Query {
sql := fmt.Sprintf("ALTER TABLE %v DROP CONSTRAINT %v", b.db.QuoteTableName(table), b.db.QuoteColumnName(name))
return b.NewQuery(sql)
}
// CreateIndex creates a Query that can be used to create an index for a table.
func (b *BaseBuilder) CreateIndex(table, name string, cols ...string) *Query {
sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v)",
b.db.QuoteColumnName(name),
b.db.QuoteTableName(table),
b.quoteColumns(cols))
return b.NewQuery(sql)
}
// CreateUniqueIndex creates a Query that can be used to create a unique index for a table.
func (b *BaseBuilder) CreateUniqueIndex(table, name string, cols ...string) *Query {
sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v)",
b.db.QuoteColumnName(name),
b.db.QuoteTableName(table),
b.quoteColumns(cols))
return b.NewQuery(sql)
}
// DropIndex creates a Query that can be used to remove the named index from a table.
func (b *BaseBuilder) DropIndex(table, name string) *Query {
sql := fmt.Sprintf("DROP INDEX %v ON %v", b.db.QuoteColumnName(name), b.db.QuoteTableName(table))
return b.NewQuery(sql)
}
// quoteColumns quotes a list of columns and concatenates them with commas.
func (b *BaseBuilder) quoteColumns(cols []string) string {
s := ""
for i, col := range cols {
if i == 0 {
s = b.db.QuoteColumnName(col)
} else {
s += ", " + b.db.QuoteColumnName(col)
}
}
return s
}