Repository navigation
Expand file tree
/
Copy pathselect.go
More file actions
447 lines (395 loc) · 14.3 KB
/
Copy pathselect.go
File metadata and controls
447 lines (395 loc) · 14.3 KB
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
// 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 (
"context"
gosql "database/sql"
"errors"
"fmt"
"reflect"
)
// SelectQuery represents a DB-agnostic SELECT query.
// It can be built into a DB-specific query by calling the Build() method.
type SelectQuery struct {
// FieldMapper maps struct field names to DB column names.
FieldMapper FieldMapFunc
// TableMapper maps structs to DB table names.
TableMapper TableMapFunc
builder Builder
ctx context.Context
selects []string
distinct bool
selectOption string
from []string
where Expression
join []JoinInfo
orderBy []string
groupBy []string
having Expression
union []UnionInfo
limit int64
offset int64
lock string
params Params
}
// JoinInfo contains the specification for a JOIN clause.
type JoinInfo struct {
Join string
Table string
On Expression
}
// UnionInfo contains the specification for a UNION clause.
type UnionInfo struct {
All bool
Query *Query
}
// NewSelectQuery creates a new SelectQuery instance.
func NewSelectQuery(builder Builder, db *DB) *SelectQuery {
return &SelectQuery{
builder: builder,
selects: []string{},
from: []string{},
join: []JoinInfo{},
orderBy: []string{},
groupBy: []string{},
union: []UnionInfo{},
limit: -1,
params: Params{},
ctx: db.ctx,
FieldMapper: db.FieldMapper,
TableMapper: db.TableMapper,
}
}
// Context returns the context associated with the query.
func (q *SelectQuery) Context() context.Context {
return q.ctx
}
// WithContext associates a context with the query.
func (q *SelectQuery) WithContext(ctx context.Context) *SelectQuery {
q.ctx = ctx
return q
}
// Select specifies the columns to be selected.
// Column names will be automatically quoted.
func (s *SelectQuery) Select(cols ...string) *SelectQuery {
s.selects = cols
return s
}
// AndSelect adds additional columns to be selected.
// Column names will be automatically quoted.
func (s *SelectQuery) AndSelect(cols ...string) *SelectQuery {
s.selects = append(s.selects, cols...)
return s
}
// Distinct specifies whether to select columns distinctively.
// By default, distinct is false.
func (s *SelectQuery) Distinct(v bool) *SelectQuery {
s.distinct = v
return s
}
// SelectOption specifies additional option that should be append to "SELECT".
func (s *SelectQuery) SelectOption(option string) *SelectQuery {
s.selectOption = option
return s
}
// From specifies which tables to select from.
// Table names will be automatically quoted.
func (s *SelectQuery) From(tables ...string) *SelectQuery {
s.from = tables
return s
}
// Where specifies the WHERE condition.
func (s *SelectQuery) Where(e Expression) *SelectQuery {
s.where = e
return s
}
// AndWhere concatenates a new WHERE condition with the existing one (if any) using "AND".
func (s *SelectQuery) AndWhere(e Expression) *SelectQuery {
s.where = And(s.where, e)
return s
}
// OrWhere concatenates a new WHERE condition with the existing one (if any) using "OR".
func (s *SelectQuery) OrWhere(e Expression) *SelectQuery {
s.where = Or(s.where, e)
return s
}
// Join specifies a JOIN clause.
// The "typ" parameter specifies the JOIN type (e.g. "INNER JOIN", "LEFT JOIN").
func (s *SelectQuery) Join(typ string, table string, on Expression) *SelectQuery {
s.join = append(s.join, JoinInfo{typ, table, on})
return s
}
// InnerJoin specifies an INNER JOIN clause.
// This is a shortcut method for Join.
func (s *SelectQuery) InnerJoin(table string, on Expression) *SelectQuery {
return s.Join("INNER JOIN", table, on)
}
// LeftJoin specifies a LEFT JOIN clause.
// This is a shortcut method for Join.
func (s *SelectQuery) LeftJoin(table string, on Expression) *SelectQuery {
return s.Join("LEFT JOIN", table, on)
}
// RightJoin specifies a RIGHT JOIN clause.
// This is a shortcut method for Join.
func (s *SelectQuery) RightJoin(table string, on Expression) *SelectQuery {
return s.Join("RIGHT JOIN", table, on)
}
// OrderBy specifies the ORDER BY clause.
// Column names will be properly quoted. A column name can contain "ASC" or "DESC" to indicate its ordering direction.
func (s *SelectQuery) OrderBy(cols ...string) *SelectQuery {
s.orderBy = cols
return s
}
// AndOrderBy appends additional columns to the existing ORDER BY clause.
// Column names will be properly quoted. A column name can contain "ASC" or "DESC" to indicate its ordering direction.
func (s *SelectQuery) AndOrderBy(cols ...string) *SelectQuery {
s.orderBy = append(s.orderBy, cols...)
return s
}
// GroupBy specifies the GROUP BY clause.
// Column names will be properly quoted.
func (s *SelectQuery) GroupBy(cols ...string) *SelectQuery {
s.groupBy = cols
return s
}
// AndGroupBy appends additional columns to the existing GROUP BY clause.
// Column names will be properly quoted.
func (s *SelectQuery) AndGroupBy(cols ...string) *SelectQuery {
s.groupBy = append(s.groupBy, cols...)
return s
}
// Having specifies the HAVING clause.
func (s *SelectQuery) Having(e Expression) *SelectQuery {
s.having = e
return s
}
// AndHaving concatenates a new HAVING condition with the existing one (if any) using "AND".
func (s *SelectQuery) AndHaving(e Expression) *SelectQuery {
s.having = And(s.having, e)
return s
}
// OrHaving concatenates a new HAVING condition with the existing one (if any) using "OR".
func (s *SelectQuery) OrHaving(e Expression) *SelectQuery {
s.having = Or(s.having, e)
return s
}
// Union specifies a UNION clause.
func (s *SelectQuery) Union(q *Query) *SelectQuery {
s.union = append(s.union, UnionInfo{false, q})
return s
}
// UnionAll specifies a UNION ALL clause.
func (s *SelectQuery) UnionAll(q *Query) *SelectQuery {
s.union = append(s.union, UnionInfo{true, q})
return s
}
// Limit specifies the LIMIT clause.
// A negative limit means no limit.
func (s *SelectQuery) Limit(limit int64) *SelectQuery {
s.limit = limit
return s
}
// Offset specifies the OFFSET clause.
// A negative offset means no offset.
func (s *SelectQuery) Offset(offset int64) *SelectQuery {
s.offset = offset
return s
}
// ForUpdate makes the query a locking read: FOR UPDATE locks the selected rows exclusively until
// the end of the transaction. The clause is placed after ORDER BY, LIMIT and OFFSET.
//
// Row locks exist only inside a transaction: outside one, the statement commits at once and the
// lock is released immediately. So a locking read must be built from a Tx (tx.Select()); built
// from a DB, whose executor is a *sql.DB, Build sets LastError on every database, SQLite included.
// A locking read cannot be combined with UNION, GROUP BY or DISTINCT (Build sets LastError); avoid
// aggregate functions too. SQLite has no row locks (a write transaction locks the whole database),
// so inside a transaction the clause is left out there. SQL Server and Oracle are not supported
// (Build sets LastError).
// Of ForUpdate, ForShare and ForUpdateSkipLocked, the last one called wins.
func (s *SelectQuery) ForUpdate() *SelectQuery {
s.lock = lockForUpdate
return s
}
// ForShare makes the query a locking read with FOR SHARE: the selected rows are locked in shared
// mode until the end of the transaction, so other transactions can read but not modify them.
// It requires MySQL 8.0+ or PostgreSQL; MySQL 5.7 and MariaDB do not support FOR SHARE.
// See ForUpdate for the rules that apply to all locking reads.
func (s *SelectQuery) ForShare() *SelectQuery {
s.lock = lockForShare
return s
}
// ForUpdateSkipLocked makes the query a locking read with FOR UPDATE SKIP LOCKED: like ForUpdate,
// but rows locked by other transactions are skipped instead of waited for. It is meant for job
// queues, where each worker claims rows the others have not taken.
// It requires MySQL 8.0+ or PostgreSQL 9.5+. See ForUpdate for the rules that apply to all
// locking reads.
func (s *SelectQuery) ForUpdateSkipLocked() *SelectQuery {
s.lock = lockForUpdateSkipLocked
return s
}
// The locking clauses set by ForUpdate, ForShare and ForUpdateSkipLocked.
const (
lockForUpdate = "FOR UPDATE"
lockForShare = "FOR SHARE"
lockForUpdateSkipLocked = "FOR UPDATE SKIP LOCKED"
)
// lockMethods names the SelectQuery method of each locking clause, for error messages.
var lockMethods = map[string]string{
lockForUpdate: "ForUpdate",
lockForShare: "ForShare",
lockForUpdateSkipLocked: "ForUpdateSkipLocked",
}
// buildLock appends the locking clause to sql, the SELECT statement with its ORDER BY and LIMIT.
func (s *SelectQuery) buildLock(qb QueryBuilder, sql string) (string, error) {
// PostgreSQL rejects these combinations; one rule for every database keeps queries portable
switch {
case len(s.union) > 0:
return sql, errors.New("a locking read cannot be combined with UNION")
case len(s.groupBy) > 0:
return sql, errors.New("a locking read cannot be combined with GROUP BY")
case s.distinct:
return sql, errors.New("a locking read cannot be combined with DISTINCT")
}
// Outside a transaction the lock is released as soon as the statement ends, so it protects
// nothing (two job-queue workers could claim the same row). Rejected on every database,
// SQLite included, so that the bug surfaces in SQLite-backed test suites too. Only a plain
// *sql.DB is rejected, so custom or instrumented executors never get a false positive.
if eb, ok := s.builder.(interface{ Executor() Executor }); ok {
if _, ok := eb.Executor().(*gosql.DB); ok {
return sql, fmt.Errorf("%v requires a transaction: outside one the lock is released when the statement ends", lockMethods[s.lock])
}
}
if _, ok := s.builder.(*SqliteBuilder); ok {
return sql, nil
}
lqb, ok := qb.(LockQueryBuilder)
if !ok {
return sql, fmt.Errorf("the query builder %T does not support locking reads", qb)
}
return lqb.BuildLock(sql, s.lock)
}
// Bind specifies the parameter values to be bound to the query.
func (s *SelectQuery) Bind(params Params) *SelectQuery {
s.params = params
return s
}
// AndBind appends additional parameters to be bound to the query.
func (s *SelectQuery) AndBind(params Params) *SelectQuery {
if len(s.params) == 0 {
s.params = params
} else {
for k, v := range params {
s.params[k] = v
}
}
return s
}
// Build builds the SELECT query and returns an executable Query object.
func (s *SelectQuery) Build() *Query {
params := Params{}
for k, v := range s.params {
params[k] = v
}
qb := s.builder.QueryBuilder()
clauses := []string{
qb.BuildSelect(s.selects, s.distinct, s.selectOption),
qb.BuildFrom(s.from),
qb.BuildJoin(s.join, params),
qb.BuildWhere(s.where, params),
qb.BuildGroupBy(s.groupBy),
qb.BuildHaving(s.having, params),
}
sql := ""
for _, clause := range clauses {
if clause != "" {
if sql == "" {
sql = clause
} else {
sql += " " + clause
}
}
}
sql = qb.BuildOrderByAndLimit(sql, s.orderBy, s.limit, s.offset)
var lockErr error
if s.lock != "" {
sql, lockErr = s.buildLock(qb, sql)
}
if union := qb.BuildUnion(s.union, params); union != "" {
sql = fmt.Sprintf("(%v) %v", sql, union)
}
q := s.builder.NewQuery(sql).Bind(params)
if lockErr != nil {
q.LastError = lockErr
}
return q
}
// One executes the SELECT query and populates the first row of the result into the specified variable.
//
// If the query does not specify a "from" clause, the method will try to infer the name of the table
// to be selected from by calling getTableName() which will return either the variable type name
// or the TableName() method if the variable implements the TableModel interface.
//
// Note that when the query has no rows in the result set, an sql.ErrNoRows will be returned.
func (s *SelectQuery) One(a interface{}) error {
if len(s.from) == 0 {
if tableName := s.TableMapper(a); tableName != "" {
s.from = []string{tableName}
}
}
return s.Build().WithContext(s.ctx).One(a)
}
// Model selects the row with the specified primary key and populates the model with the row data.
//
// The model variable should be a pointer to a struct. If the query does not specify a "from" clause,
// it will use the model struct to determine which table to select data from. It will also use the model
// to infer the name of the primary key column. Only simple primary key is supported. For composite primary keys,
// please use Where() to specify the filtering condition.
func (s *SelectQuery) Model(pk, model interface{}) error {
t := reflect.TypeOf(model)
if t.Kind() == reflect.Ptr { //nolint:govet // reflect.Ptr is valid, govet toolchain mismatch
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return VarTypeError("must be a pointer to a struct")
}
si := getStructInfo(t, s.FieldMapper)
if len(si.pkNames) == 1 {
return s.AndWhere(HashExp{si.nameMap[si.pkNames[0]].dbName: pk}).One(model)
}
if len(si.pkNames) == 0 {
return MissingPKError
}
return CompositePKError
}
// All executes the SELECT query and populates all rows of the result into a slice.
//
// Note that the slice must be passed in as a pointer.
//
// If the query does not specify a "from" clause, the method will try to infer the name of the table
// to be selected from by calling getTableName() which will return either the type name of the slice elements
// or the TableName() method if the slice element implements the TableModel interface.
func (s *SelectQuery) All(slice interface{}) error {
if len(s.from) == 0 {
if tableName := s.TableMapper(slice); tableName != "" {
s.from = []string{tableName}
}
}
return s.Build().WithContext(s.ctx).All(slice)
}
// Rows builds and executes the SELECT query and returns a Rows object for data retrieval purpose.
// This is a shortcut to SelectQuery.Build().Rows()
func (s *SelectQuery) Rows() (*Rows, error) {
return s.Build().WithContext(s.ctx).Rows()
}
// Row builds and executes the SELECT query and populates the first row of the result into the specified variables.
// This is a shortcut to SelectQuery.Build().Row()
func (s *SelectQuery) Row(a ...interface{}) error {
return s.Build().WithContext(s.ctx).Row(a...)
}
// Column builds and executes the SELECT statement and populates the first column of the result into a slice.
// Note that the parameter must be a pointer to a slice.
// This is a shortcut to SelectQuery.Build().Column()
func (s *SelectQuery) Column(a interface{}) error {
return s.Build().WithContext(s.ctx).Column(a)
}