forked from samsarahq/thunder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
batch.go
113 lines (100 loc) · 2.8 KB
/
batch.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
package sqlgen
import (
"bytes"
"sort"
)
// makeBatchQuery combines a set of filters into a single SQL that matches any
// of the filters, in order to fulfill many independent queries with a single
// SQL SELECT.
//
// Different filters with equal sets of columns are combined into grouped IN
// queries. For example, the filters
// {"id": 10}
// {"id": 20}
// {"name": "Bob", "city": "San Francisco"}
// will get combined to form the query
// WHERE ((city, name)) in ("San Francisco", "Bob") OR id in (10, 20)
// (except with parameter substitution.)
func makeBatchQuery(filters []Filter) (string, []interface{}) {
// A batchQueryGroup holds all value tuples for a given set of columns in the
// WHERE statement.
type batchQueryGroup struct {
columns []string
tuples [][]interface{}
}
// Put every filter in its group.
groups := make(map[string]*batchQueryGroup)
for _, filter := range filters {
// If any of the filters is empty (and matches all rows), short-circuit and
// return an empty WHERE clause. This is special case logic because you can't
// create an IN query with 0 columns.
if len(filter) == 0 {
return "", nil
}
// Figure out this filters' group.
columns := extractColumns(filter)
key := columnsKey(columns)
// Maybe create the group.
group, ok := groups[key]
if !ok {
group = &batchQueryGroup{
columns: columns,
}
groups[key] = group
}
// Add the filter to the group.
group.tuples = append(group.tuples, extractValuesTuple(filter, columns))
}
// Sort the groups by their key (for deterministic tests.)
groupKeys := make([]string, 0, len(groups))
for group := range groups {
groupKeys = append(groupKeys, group)
}
sort.Strings(groupKeys)
// Build the WHERE clause one group at a time.
var clause bytes.Buffer
var args []interface{}
for i, key := range groupKeys {
group := groups[key]
// Insert OR between groups.
if i > 0 {
clause.WriteString(" OR ")
}
if len(group.columns) == 1 {
column := group.columns[0]
clause.WriteString(column)
clause.WriteString(" IN (")
for j, tuple := range group.tuples {
// Separate tuples with commas.
if j > 0 {
clause.WriteString(", ")
}
// Write (?, ?, ?) string for the tuple, and append the arguments.
clause.WriteString("?")
args = append(args, tuple...)
}
clause.WriteString(")")
} else {
for i, tuple := range group.tuples {
if i > 0 {
clause.WriteString(" OR ")
}
if len(group.columns) > 1 {
clause.WriteString("(")
}
for j, column := range group.columns {
if j > 0 {
clause.WriteString(" AND ")
}
clause.WriteString(column)
clause.WriteString("=?")
}
args = append(args, tuple...)
if len(group.columns) > 1 {
clause.WriteString(")")
}
}
}
}
return clause.String(), args
}