-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathquery.go
More file actions
302 lines (250 loc) · 6.13 KB
/
query.go
File metadata and controls
302 lines (250 loc) · 6.13 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
package query
import (
"strconv"
"strings"
)
type statement uint
// Option is the type for the first class functions that should be used for
// modifying a Query as it is being built. This will be passed the latest
// state of the Query, and should return that same Query once any modifications
// have been made.
type Option func(Query) Query
// Query contains the state of a Query that is being built. The only way this
// should be modified is via the use of the Option first class function.
type Query struct {
stmt statement
table string
exprs []Expr
clauses []clause
args []interface{}
}
//go:generate stringer -type statement -linecomment
const (
_Stmt statement = iota //
_Delete // DELETE
_Insert // INSERT
_Select // SELECT
_Update // UPDATE
_SelectDistinct // SELECT DISTINCT
_SelectDistinctOn // SELECT DISTINCT ON
)
// Delete builds up a DELETE query on the given table applying the given
// options.
func Delete(table string, opts ...Option) Query {
q := Query{
stmt: _Delete,
table: table,
}
for _, opt := range opts {
q = opt(q)
}
return q
}
// Insert builds up an INSERT query on the given table using the given leading
// expression, and applying the given options.
func Insert(table string, expr Expr, opts ...Option) Query {
q := Query{
stmt: _Insert,
table: table,
exprs: []Expr{expr},
}
for _, opt := range opts {
q = opt(q)
}
return q
}
// Select will build up a SELECT query using the given leading expression, and
// applying the given options.
func Select(expr Expr, opts ...Option) Query {
q := Query{
stmt: _Select,
exprs: []Expr{expr},
}
for _, opt := range opts {
q = opt(q)
}
return q
}
func SelectDistinct(expr Expr, opts ...Option) Query {
q := Query{
stmt: _SelectDistinct,
exprs: []Expr{expr},
}
for _, opt := range opts {
q = opt(q)
}
return q
}
func SelectDistinctOn(cols []string, expr Expr, opts ...Option) Query {
q := Query{
stmt: _SelectDistinctOn,
exprs: []Expr{
listExpr{
items: cols,
wrap: true,
},
expr,
},
}
for _, opt := range opts {
q = opt(q)
}
return q
}
// Update will build up an UPDATE query on the given table applying the given
// options.
func Update(table string, opts ...Option) Query {
q := Query{
stmt: _Update,
table: table,
}
for _, opt := range opts {
q = opt(q)
}
return q
}
// Union returns a new Query that applies the UNION clause to all fo the given
// queries. This allows for multiple queries to be used within a single query.
func Union(queries ...Query) Query {
var q0 Query
for _, q := range queries {
q0.args = append(q0.args, q.args...)
q0.clauses = append(q0.clauses, unionClause{
q: q,
})
}
return q0
}
// Options applies all of the given options to the current query being built.
func Options(opts ...Option) Option {
return func(q Query) Query {
for _, opt := range opts {
q = opt(q)
}
return q
}
}
// conj returns the string that should be used for conjoining multiple clauses
// of the same type.
func (q Query) conj(cl clause) string {
if cl == nil {
return ""
}
switch v := cl.(type) {
case whereClause:
return " " + v.conjunction + " "
case unionClause:
return " " + cl.kind().String() + " "
case setClause, valuesClause:
return ", "
case orderClause:
return ", "
default:
return " "
}
}
// buildInitial builds up the initial query using ? as the placeholder. This
// will correctly wrap the portions of the query in parenthese depending on the
// clauses in the query, and how these clauses are conjoined.
func (q Query) buildInitial() string {
var buf strings.Builder
buf.WriteString(q.stmt.String())
switch q.stmt {
case _Insert:
buf.WriteString(" INTO " + q.table)
case _Update:
buf.WriteString(" " + q.table + " ")
case _Delete:
buf.WriteString(" FROM " + q.table + " ")
}
for i, expr := range q.exprs {
buf.WriteByte(' ')
if q.stmt == _Insert {
buf.WriteByte('(')
}
buf.WriteString(expr.Build())
if q.stmt == _Insert {
buf.WriteByte(')')
}
if q.stmt == _SelectDistinctOn && i == 0 {
continue
}
buf.WriteByte(' ')
}
clauses := make(map[clauseKind]struct{})
end := len(q.clauses) - 1
for i, cl := range q.clauses {
var (
prev clause
next clause
)
if i > 0 {
prev = q.clauses[i-1]
}
if i < end {
next = q.clauses[i+1]
}
kind := cl.kind()
if kind != _UnionClause {
// Write the string of the clause kind only once, this avoids something
// like multiple WHERE clauses being built into the query.
if _, ok := clauses[kind]; !ok {
clauses[kind] = struct{}{}
buf.WriteString(kind.String() + " ")
if kind == _WhereClause {
buf.WriteByte('(')
}
}
}
buf.WriteString(cl.Build())
if next != nil {
conj := q.conj(next)
// Clauses are wrapped if the next clause is different from the
// current one, or if the conjoining string is different for the
// next clause.
if next.kind() == kind {
wrap := false
if prev != nil {
// Wrap the clause in parentheses if we have a different
// conjunction string.
wrap = (prev.kind() == kind) && (conj != q.conj(cl))
}
if wrap {
buf.WriteByte(')')
}
buf.WriteString(conj)
if wrap {
buf.WriteByte('(')
}
} else {
if kind == _WhereClause {
buf.WriteByte(')')
}
buf.WriteByte(' ')
}
}
if i == end && kind == _WhereClause {
buf.WriteByte(')')
}
}
return buf.String()
}
// Args returns a slice of all the arguments that have been added to the given
// query.
func (q Query) Args() []interface{} { return q.args }
// Build builds up the query. It will initially create a query using ? as the
// placeholder for arguments. Once built up it will replace the ? with $n where
// n is the number of the argument.
func (q Query) Build() string {
s := q.buildInitial()
query := make([]byte, 0, len(s))
param := int64(0)
for i := strings.Index(s, "?"); i != -1; i = strings.Index(s, "?") {
param++
query = append(query, s[:i]...)
query = append(query, '$')
query = strconv.AppendInt(query, param, 10)
s = s[i+1:]
}
return string(append(query, []byte(s)...))
}