/
query.go
224 lines (171 loc) · 6.77 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
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
package mango
import (
"encoding/json"
"unicode"
)
// This package provides utility structures to build mango queries
////////////////////////////////////////////////////////////////
// Filter
///////////////////////////////////////////////////////////////
// ValueOperator is an operator between a field and a value
type ValueOperator string
// ne ($ne) checks that field != value
const ne ValueOperator = "$ne"
// gt ($gt) checks that field > value
const gt ValueOperator = "$gt"
// gte ($gte) checks that field >= value
const gte ValueOperator = "$gte"
// lt ($lt) checks that field < value
const lt ValueOperator = "$lt"
// lte ($lte) checks that field <= value
const lte ValueOperator = "$lte"
// exists ($exists) checks that the field exists (or is missing)
const exists ValueOperator = "$exists"
// in ($in) checks that the field value equals one of the values
const in ValueOperator = "$in"
// LogicOperator is an operator between two filters
type LogicOperator string
// and ($and) checks that filter && filter2
const and LogicOperator = "$and"
// not ($not) checks that !filter
const not LogicOperator = "$not"
// or ($or) checks that filter1 || filter2 || ...
const or LogicOperator = "$or"
// nor ($nor) checks that !(filter1 || filter2 || ...)
const nor LogicOperator = "$nor"
// A Filter is a filter on documents, to be passed
// as the selector of a couchdb.FindRequest
// In the future, we might add go-side validation
// but we will need to duplicate the couchdb UCA algorithm
type Filter interface {
json.Marshaler
ToMango() Map
}
// Map is an alias for map[string]interface{}
type Map map[string]interface{}
// ToMango implements the Filter interface on Map
// it returns the map itself
func (m Map) ToMango() Map {
return m
}
// MarshalJSON returns a byte json representation of the map
func (m Map) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}(m))
}
// valueFilter is a filter on a single field
type valueFilter struct {
field string
op ValueOperator
value interface{}
}
// ToMango implements the Filter interface on valueFilter
// it returns a map, either `{field: value}` or `{field: {$op: value}}`
func (vf valueFilter) ToMango() Map {
return makeMap(vf.field, makeMap(string(vf.op), vf.value))
}
func (vf valueFilter) MarshalJSON() ([]byte, error) {
return json.Marshal(vf.ToMango())
}
// logicFilter is a combination of filters with logic operator
type logicFilter struct {
op LogicOperator
filters []Filter
}
// ToMango implements the Filter interface on logicFilter
// We could add some logic to make $and queries more readable
// For instance
// {"$and": [{"field": {"$lt":6}}, {"field": {"$gt":3}}]
// ---> {"field": {"$lt":6, "$gt":3}
// but it doesnt improve performances.
func (lf logicFilter) ToMango() Map {
// special case, $not has an arity of one
if lf.op == not {
return makeMap(string(lf.op), lf.filters[0].ToMango())
}
// all other LogicOperator works on arrays
filters := make([]Map, len(lf.filters))
for i, v := range lf.filters {
filters[i] = v.ToMango()
}
return makeMap(string(lf.op), filters)
}
func (lf logicFilter) MarshalJSON() ([]byte, error) {
return json.Marshal(lf.ToMango())
}
// ensure ValueFilter & LogicFilter match FilterInterface
var _ Filter = (*valueFilter)(nil)
var _ Filter = (*logicFilter)(nil)
// Some Filter creation function
// And returns a filter combining several filters
func And(filters ...Filter) Filter { return logicFilter{and, filters} }
// Or returns a filter combining several filters
func Or(filters ...Filter) Filter { return logicFilter{or, filters} }
// Nor returns a filter combining several filters
func Nor(filters ...Filter) Filter { return logicFilter{nor, filters} }
// In returns a filter that checks if the field is equal to one of the values
func In(field string, values []interface{}) Filter { return &valueFilter{field, in, values} }
// Not returns a filter inversing another filter
func Not(filter Filter) Filter { return logicFilter{not, []Filter{filter}} }
// Exists returns a filter that check that the document has this field
func Exists(field string) Filter { return &valueFilter{field, exists, true} }
// NotExists returns a filter that check that the document does not have this field
func NotExists(field string) Filter { return &valueFilter{field, exists, false} }
// Equal returns a filter that check if a field == value
func Equal(field string, value interface{}) Filter { return makeMap(field, value) }
// NotEqual returns a filter that check if a field != value
func NotEqual(field string, value interface{}) Filter { return &valueFilter{field, ne, value} }
// Gt returns a filter that check if a field > value
func Gt(field string, value interface{}) Filter { return &valueFilter{field, gt, value} }
// Gte returns a filter that check if a field >= value
func Gte(field string, value interface{}) Filter { return &valueFilter{field, gte, value} }
// Lt returns a filter that check if a field < value
func Lt(field string, value interface{}) Filter { return &valueFilter{field, lt, value} }
// Lte returns a filter that check if a field <= value
func Lte(field string, value interface{}) Filter { return &valueFilter{field, lte, value} }
// Between returns a filter that check if v1 <= field < v2
func Between(field string, v1 interface{}, v2 interface{}) Filter {
return &logicFilter{op: and, filters: []Filter{
&valueFilter{field, gte, v1},
&valueFilter{field, lt, v2},
}}
}
// MaxString is the unicode character \uFFFF, useful as an upperbound for
// queryies
const MaxString = string(unicode.MaxRune)
// StartWith returns a filter that check if field's string value start with prefix
func StartWith(field string, prefix string) Filter {
return Between(field, prefix, prefix+MaxString)
}
////////////////////////////////////////////////////////////////
// Sort
///////////////////////////////////////////////////////////////
// SortDirection can be either ASC or DESC
type SortDirection string
// Asc is the ascending sorting order
const Asc SortDirection = "asc"
// Desc is the descending sorting order
const Desc SortDirection = "desc"
// SortBy is a sorting rule to be used as the sort of a couchdb.FindRequest
// a list of (field, direction) combination.
type SortBy []SortByField
// SortByField is a sorting rule to be used as the sort for a pair of (field,
// direction).
type SortByField struct {
Field string
Direction SortDirection
}
// MarshalJSON implements json.Marshaller on SortBy
// it will returns a json array [field, direction]
func (s SortBy) MarshalJSON() ([]byte, error) {
asSlice := make([]Map, len(s))
for i, f := range s {
asSlice[i] = makeMap(f.Field, string(f.Direction))
}
return json.Marshal(asSlice)
}
// utility function to create a map with a single key
func makeMap(key string, value interface{}) Map {
out := make(Map)
out[key] = value
return out
}