forked from go-ozzo/ozzo-dbx
-
Notifications
You must be signed in to change notification settings - Fork 29
/
select.go
419 lines (364 loc) · 11.7 KB
/
select.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
// 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"
"fmt"
"reflect"
)
// BuildHookFunc defines a callback function that is executed on Query creation.
type BuildHookFunc func(q *Query)
// 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
buildHook BuildHookFunc
selects []string
distinct bool
selectOption string
from []string
where Expression
join []JoinInfo
orderBy []string
groupBy []string
having Expression
union []UnionInfo
limit int64
offset int64
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,
}
}
// WithBuildHook runs the provided hook function with the query created on Build().
func (q *SelectQuery) WithBuildHook(fn BuildHookFunc) *SelectQuery {
q.buildHook = fn
return q
}
// 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
}
// 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)
if union := qb.BuildUnion(s.union, params); union != "" {
sql = fmt.Sprintf("(%v) %v", sql, union)
}
query := s.builder.NewQuery(sql).Bind(params).WithContext(s.ctx)
if s.buildHook != nil {
s.buildHook(query)
}
return query
}
// 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().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 {
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().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().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().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().Column(a)
}
// QueryInfo represents a debug/info struct with exported SelectQuery fields.
type QueryInfo struct {
Builder Builder
Selects []string
Distinct bool
SelectOption string
From []string
Where Expression
Join []JoinInfo
OrderBy []string
GroupBy []string
Having Expression
Union []UnionInfo
Limit int64
Offset int64
Params Params
Context context.Context
BuildHook BuildHookFunc
}
// Info exports common SelectQuery fields allowing to inspect the
// current select query options.
func (s *SelectQuery) Info() *QueryInfo {
return &QueryInfo{
Builder: s.builder,
Selects: s.selects,
Distinct: s.distinct,
SelectOption: s.selectOption,
From: s.from,
Where: s.where,
Join: s.join,
OrderBy: s.orderBy,
GroupBy: s.groupBy,
Having: s.having,
Union: s.union,
Limit: s.limit,
Offset: s.offset,
Params: s.params,
Context: s.ctx,
BuildHook: s.buildHook,
}
}