-
Notifications
You must be signed in to change notification settings - Fork 27
/
select_statement.go
371 lines (318 loc) · 10.3 KB
/
select_statement.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
package godb
import (
"database/sql"
"fmt"
"time"
"github.com/samonzeweb/godb/adapters"
)
// SelectStatement is a SELECT sql statement builder.
// Initialize it with the SelectFrom method.
//
// Examples :
// err := db.SelecFrom("bar").
// Columns("foo", "baz").
// Where("foo > 2").
// Do(&target)
type SelectStatement struct {
db *DB
error error
distinct bool
columns []string
areColumnsFromStruct bool
columnAliases map[string]string
fromTables []string
joins []*joinPart
where []*Condition
groupBy []string
having []*Condition
orderBy []string
limit *int
offset *int
suffixes []string
}
// joinPart describes a sql JOIN clause.
type joinPart struct {
joinType string
tableName string
as string
on *Condition
}
// SelectFrom initializes a SELECT statement builder.
func (db *DB) SelectFrom(tableNames ...string) *SelectStatement {
ss := &SelectStatement{db: db, columnAliases: map[string]string{}}
return ss.From(tableNames...)
}
// From adds table to the select statement. It can be called multiple times.
func (ss *SelectStatement) From(tableNames ...string) *SelectStatement {
ss.fromTables = append(ss.fromTables, tableNames...)
return ss
}
// Columns adds columns to select. Multple calls of columns are allowed.
func (ss *SelectStatement) Columns(columns ...string) *SelectStatement {
if ss.areColumnsFromStruct {
ss.error = fmt.Errorf("you can't mix Columns and ColumnsFromStruct to build a select query")
return ss
}
ss.columns = append(ss.columns, columns...)
return ss
}
// ColumnsFromStruct adds columns to select, extrating them from the
// given struct (or slice of struct). Always use a pointer as argument.
// You can't mix the use of ColumnsFromStruct and Columns methods.
func (ss *SelectStatement) ColumnsFromStruct(record interface{}) *SelectStatement {
if len(ss.columns) > 0 {
ss.error = fmt.Errorf("you can't mix Columns and ColumnsFromStruct to build a select query")
return ss
}
ss.areColumnsFromStruct = true
recordInfo, err := buildRecordDescription(record)
if err != nil {
ss.error = err
} else {
columns := ss.db.quoteAll(recordInfo.structMapping.GetAllColumnsNames())
ss.columns = append(ss.columns, columns...)
}
return ss
}
// ColumnAlias allows to define alias for a column. Useful if selectable
// columns are built with ColumnsFromStruct and when using joins.
func (ss *SelectStatement) ColumnAlias(column, alias string) *SelectStatement {
ss.columnAliases[ss.db.quote(alias)] = ss.db.quote(column)
return ss
}
// Distinct adds DISTINCT keyword the the generated statement.
func (ss *SelectStatement) Distinct() *SelectStatement {
ss.distinct = true
return ss
}
// InnerJoin adds as INNER JOIN clause, which will be inserted between FROM and WHERE
// clauses.
func (ss *SelectStatement) InnerJoin(tableName string, as string, on *Condition) *SelectStatement {
return ss.addJoin("INNER JOIN", tableName, as, on)
}
// LeftJoin adds a LEFT JOIN clause, which will be inserted between FROM and WHERE
// clauses.
func (ss *SelectStatement) LeftJoin(tableName string, as string, on *Condition) *SelectStatement {
return ss.addJoin("LEFT JOIN", tableName, as, on)
}
// addJoin adds a join clause.
func (ss *SelectStatement) addJoin(joinType string, tableName string, as string, on *Condition) *SelectStatement {
join := &joinPart{
joinType: joinType,
tableName: tableName,
as: as,
on: on,
}
ss.joins = append(ss.joins, join)
return ss
}
// Where adds a condition using string and arguments.
func (ss *SelectStatement) Where(sql string, args ...interface{}) *SelectStatement {
return ss.WhereQ(Q(sql, args...))
}
// WhereQ adds a simple or complex predicate generated with Q and
// conjunctions.
func (ss *SelectStatement) WhereQ(condition *Condition) *SelectStatement {
ss.where = append(ss.where, condition)
return ss
}
// GroupBy adds a GROUP BY clause. You can call GroupBy multiple times.
func (ss *SelectStatement) GroupBy(groupBy string) *SelectStatement {
ss.groupBy = append(ss.groupBy, groupBy)
return ss
}
// Having adds a HAVING clause with a condition build with a sql string and
// its arguments (like Where).
func (ss *SelectStatement) Having(sql string, args ...interface{}) *SelectStatement {
return ss.HavingQ(Q(sql, args...))
}
// HavingQ adds a simple or complex predicate generated with Q and
// conjunctions (like WhereQ).
func (ss *SelectStatement) HavingQ(condition *Condition) *SelectStatement {
ss.having = append(ss.having, condition)
return ss
}
// OrderBy adds an expression for the ORDER BY clause.
// You can call GroupBy multiple times.
func (ss *SelectStatement) OrderBy(orderBy string) *SelectStatement {
ss.orderBy = append(ss.orderBy, orderBy)
return ss
}
// Offset specifies the value for the OFFSET clause.
func (ss *SelectStatement) Offset(offset int) *SelectStatement {
ss.offset = new(int)
*ss.offset = offset
return ss
}
// Limit specifies the value for the LIMIT clause.
func (ss *SelectStatement) Limit(limit int) *SelectStatement {
ss.limit = new(int)
*ss.limit = limit
return ss
}
// Suffix adds an expression to suffix the query.
func (ss *SelectStatement) Suffix(suffix string) *SelectStatement {
ss.suffixes = append(ss.suffixes, suffix)
return ss
}
// ToSQL returns a string with the SQL request (containing placeholders),
// the arguments slices, and an error.
func (ss *SelectStatement) ToSQL() (string, []interface{}, error) {
if ss.error != nil {
return "", nil, ss.error
}
sqlWhereLength, argsWhereLength, err := sumOfConditionsLengths(ss.where)
if err != nil {
return "", nil, err
}
sqlHavingLength, argsHavingLength, err := sumOfConditionsLengths(ss.having)
if err != nil {
return "", nil, err
}
sqlBuffer := newSQLBuffer(
ss.db.adapter,
sqlWhereLength+sqlHavingLength+64,
argsWhereLength+argsHavingLength+4,
)
sqlBuffer.Write("SELECT ")
if ss.distinct {
sqlBuffer.Write("DISTINCT ")
}
sqlBuffer.writeColumns(ss.columns).
writeFrom(ss.fromTables...).
writeJoins(ss.joins).
writeWhere(ss.where).
writeGroupByAndHaving(ss.groupBy, ss.having).
writeOrderBy(ss.orderBy)
offsetFirst := false
if limitOffsetOrderer, ok := ss.db.adapter.(adapters.LimitOffsetOrderer); ok {
offsetFirst = limitOffsetOrderer.IsOffsetFirst()
}
if offsetFirst {
// Offset is before limit
sqlBuffer.writeOffset(ss.offset).
writeLimit(ss.limit)
} else {
// Limit is before offset (default case)
sqlBuffer.writeLimit(ss.limit).
writeOffset(ss.offset)
}
sqlBuffer.writeStringsWithSpaces(ss.suffixes)
return sqlBuffer.SQL(), sqlBuffer.Arguments(), sqlBuffer.Err()
}
// Do executes the select statement.
// The record argument has to be a pointer to a struct or a slice.
// If no columns is defined for current select statement, all columns are
// added from record parameter's struct.
// If the argument is not a slice, a row is expected, and Do returns
// sql.ErrNoRows is none where found.
func (ss *SelectStatement) Do(record interface{}) error {
if ss.error != nil {
return ss.error
}
recordInfo, err := buildRecordDescription(record)
if err != nil {
return err
}
// If no columns defined for selection, get all columns (SELECT * FROM)
if len(ss.columns) == 0 {
ss.areColumnsFromStruct = true
columns := ss.db.quoteAll(recordInfo.structMapping.GetAllColumnsNames())
ss.columns = append(ss.columns, columns...)
}
// Replace columns with aliases
for i := range ss.columns {
if c, ok := ss.columnAliases[ss.columns[i]]; ok {
ss.columns[i] = fmt.Sprintf("%s as %s", c, ss.columns[i])
}
}
// the function which will return the pointers according to the given columns
f := func(record interface{}, columns []string) ([]interface{}, error) {
var pointers []interface{}
var err error
if ss.areColumnsFromStruct {
pointers = recordInfo.structMapping.GetAllFieldsPointers(record)
} else {
pointers, err = recordInfo.structMapping.GetPointersForColumns(record, columns...)
}
return pointers, err
}
return ss.do(recordInfo, f)
}
// do executes the statement and fill the struct or slice given through the
// recordDescription.
func (ss *SelectStatement) do(recordInfo *recordDescription, pointersGetter pointersGetter) error {
if !recordInfo.isSlice {
// Only one row is requested
ss.Limit(1)
// Some DB require an offset if a limit is specified (MS SQL Server)
if ss.offset == nil {
ss.Offset(0)
}
// Some DB require an order by if offset and limit are used
// (still MS SQL Server)
if len(ss.orderBy) == 0 {
keysColumns := recordInfo.structMapping.GetKeyColumnsNames()
for _, keyColumn := range keysColumns {
ss.OrderBy(keyColumn)
}
}
}
sqlQuery, args, err := ss.ToSQL()
if err != nil {
return err
}
rowsCount, err := ss.db.doSelectOrWithReturning(sqlQuery, args, recordInfo, pointersGetter)
if err != nil {
return err
}
// When a single instance is requested but not found, sql.ErrNoRows is
// returned like QueryRow in database/sql package.
if !recordInfo.isSlice && rowsCount == 0 {
err = sql.ErrNoRows
}
return err
}
// Scanx runs the request and scans results to dest params
func (ss *SelectStatement) Scanx(dest ...interface{}) error {
stmt, args, err := ss.ToSQL()
if err != nil {
return err
}
stmt = ss.db.replacePlaceholders(stmt)
startTime := time.Now()
queryable, err := ss.db.getQueryable(stmt)
if err != nil {
ss.db.logExecutionErr(err, stmt, args)
return err
}
err = queryable.QueryRow(args...).Scan(dest...)
consumedTime := timeElapsedSince(startTime)
ss.db.addConsumedTime(consumedTime)
ss.db.logExecution(consumedTime, stmt, args)
if err != nil {
ss.db.logExecutionErr(err, stmt, args)
return err
}
return nil
}
// Count runs the request with COUNT(*) (remove others columns)
// and returns the count.
func (ss *SelectStatement) Count() (int64, error) {
ss.columns = ss.columns[:0]
ss.Columns("COUNT(*)")
var count int64
err := ss.Scanx(&count)
return count, err
}
// DoWithIterator executes the select query and returns an Iterator allowing
// the caller to fetch rows one at a time.
// Warning : it does not use an existing transation to avoid some pitfalls with
// drivers, nor the prepared statement.
func (ss *SelectStatement) DoWithIterator() (Iterator, error) {
sqlQuery, args, err := ss.ToSQL()
if err != nil {
return nil, err
}
return ss.db.doWithIterator(sqlQuery, args)
}