/
query.go
153 lines (123 loc) · 2.93 KB
/
query.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
package mgx
import (
"errors"
"fmt"
"strings"
"sync"
"go.mongodb.org/mongo-driver/bson"
)
var (
queryCache = make(map[string]interface{})
queryLock = sync.RWMutex{}
)
// MustParseQuery creates bson request from specified JSON and parameters. Same as ParseQuery
// but panics if there was an error during parsing JSON..
func MustParseQuery(query string, keyValues ...interface{}) interface{} {
i, err := ParseQuery(query, keyValues...)
if err != nil {
panic(fmt.Sprintf("can not compile query: \"%s\", error: %v", query, err))
}
return i
}
// ParseQuery creates bson document from specified JSON and specified parameters.ParseQuery uses
// internal cache so query will be decoded only once, and then cached values will be used.
func ParseQuery(query string, keyValues ...interface{}) (interface{}, error) {
queryLock.RLock()
cq, ok := queryCache[query]
queryLock.RUnlock()
if ok {
cq = clone(cq)
err := setParams(cq, keyValues...)
return cq, err
}
queryLock.Lock()
defer queryLock.Unlock()
// double check resource locking
cq, ok = queryCache[query]
if ok {
cq = clone(cq)
err := setParams(cq, keyValues...)
return cq, err
}
var i interface{}
err := bson.UnmarshalExtJSON([]byte(query), false, &i)
if err != nil {
return nil, err
}
queryCache[query] = i
i = clone(i)
err = setParams(i, keyValues...)
return i, err
}
func makeParamMap(keyValues ...interface{}) (map[string]interface{}, error) {
if len(keyValues) == 0 {
return nil, nil
}
if len(keyValues)%2 != 0 {
return nil, errors.New("keyValues should be pairs of string key and any value")
}
prmMap := make(map[string]interface{}, len(keyValues)/2)
for i := 0; i < len(keyValues); i += 2 {
s, ok := keyValues[i].(string)
if !ok {
return nil, fmt.Errorf("parameter key %v must be string", keyValues[i])
}
prmMap[s] = keyValues[i+1]
}
return prmMap, nil
}
func setParams(query interface{}, keyValues ...interface{}) error {
prmMap, err := makeParamMap(keyValues...)
if err != nil {
return err
}
traverse(query, prmMap)
return nil
}
func traverse(node interface{}, prmMap map[string]interface{}) {
switch v := node.(type) {
case bson.D:
for i, e := range v {
s, ok := e.Value.(string)
if ok && strings.HasPrefix(s, "$") {
pv, ok := prmMap[s]
if !ok {
continue
}
v[i] = bson.E{Key: e.Key, Value: pv}
continue
}
traverse(e.Value, prmMap)
}
case bson.A:
for i, e := range v {
s, ok := e.(string)
if ok && strings.HasPrefix(s, "$") {
pv, ok := prmMap[s]
if !ok {
continue
}
v[i] = pv
continue
}
traverse(e, prmMap)
}
}
}
func clone(node interface{}) interface{} {
switch v := node.(type) {
case bson.D:
c := make(bson.D, 0, len(v))
for _, e := range v {
c = append(c, bson.E{Key: e.Key, Value: clone(e.Value)})
}
return c
case bson.A:
a := make(bson.A, 0, len(v))
for _, e := range v {
a = append(a, clone(e))
}
return a
}
return node
}