OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package memory | 5 package memory |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | |
8 "errors" | 9 "errors" |
9 "fmt" | 10 "fmt" |
10 "math" | 11 "math" |
11 "strings" | 12 "strings" |
12 | 13 |
13 ds "github.com/luci/gae/service/datastore" | 14 ds "github.com/luci/gae/service/datastore" |
14 "github.com/luci/gkvlite" | |
15 ) | 15 ) |
16 | 16 |
17 const MaxQueryComponents = 100 | |
dnj (Google)
2015/08/14 21:01:35
Is this a hard limit, or one you chose? Either way
iannucci
2015/08/14 22:25:47
It has the same amount of documentation as the con
| |
18 | |
19 var errQueryDone = errors.New("query is done") | |
20 | |
17 type queryOp int | 21 type queryOp int |
18 | 22 |
19 const ( | 23 const ( |
20 qInvalid queryOp = iota | 24 qInvalid queryOp = iota |
21 qEqual | 25 qEqual |
22 qLessThan | 26 qLessThan |
23 qLessEq | 27 qLessEq |
24 qGreaterEq | 28 qGreaterEq |
25 qGreaterThan | 29 qGreaterThan |
26 ) | 30 ) |
27 | 31 |
28 func (o queryOp) isEQOp() bool { | |
29 return o == qEqual | |
30 } | |
31 | |
32 func (o queryOp) isINEQOp() bool { | |
33 return o >= qLessThan && o <= qGreaterThan | |
34 } | |
35 | |
36 var queryOpMap = map[string]queryOp{ | 32 var queryOpMap = map[string]queryOp{ |
37 "=": qEqual, | 33 "=": qEqual, |
38 "<": qLessThan, | 34 "<": qLessThan, |
39 "<=": qLessEq, | 35 "<=": qLessEq, |
40 ">=": qGreaterEq, | 36 ">=": qGreaterEq, |
41 ">": qGreaterThan, | 37 ">": qGreaterThan, |
42 } | 38 } |
43 | 39 |
44 type queryFilter struct { | 40 type queryFilter struct { |
45 prop string | 41 prop string |
46 op queryOp | 42 op queryOp |
47 value interface{} | 43 value interface{} |
48 } | 44 } |
49 | 45 |
50 func parseFilter(f string, v interface{}) (ret queryFilter, err error) { | 46 func parseFilter(f string) (prop string, op queryOp, err error) { |
51 toks := strings.SplitN(strings.TrimSpace(f), " ", 2) | 47 toks := strings.SplitN(strings.TrimSpace(f), " ", 2) |
52 if len(toks) != 2 { | 48 if len(toks) != 2 { |
53 err = errors.New("datastore: invalid filter: " + f) | 49 err = errors.New("datastore: invalid filter: " + f) |
54 } else { | 50 } else { |
55 » » op := queryOpMap[toks[1]] | 51 » » op = queryOpMap[toks[1]] |
56 if op == qInvalid { | 52 if op == qInvalid { |
57 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f) | 53 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f) |
58 } else { | 54 } else { |
59 » » » ret.prop = toks[0] | 55 » » » prop = toks[0] |
60 » » » ret.op = op | |
61 » » » ret.value = v | |
62 } | 56 } |
63 } | 57 } |
64 return | 58 return |
65 } | 59 } |
66 | 60 |
67 type queryCursor string | 61 type queryCursor string |
68 | 62 |
69 func (q queryCursor) String() string { return string(q) } | 63 func (q queryCursor) String() string { return string(q) } |
70 func (q queryCursor) Valid() bool { return q != "" } | 64 func (q queryCursor) Valid() bool { return q != "" } |
71 | 65 |
66 type queryIneqFilter struct { | |
67 prop string | |
68 | |
69 low string | |
70 high string | |
71 } | |
72 | |
73 func increment(bstr string, positive bool) string { | |
74 lastIdx := len(bstr) - 1 | |
dnj (Google)
2015/08/14 21:01:35
Can we have an empty bstr?
iannucci
2015/08/14 22:25:47
nope
| |
75 last := bstr[lastIdx] | |
76 if positive { | |
77 if last == 0xFF { | |
78 return bstr + "\x00" | |
79 } | |
80 return bstr[:lastIdx-1] + string(last+1) | |
81 } else { | |
82 if last == 0 { | |
83 return bstr[:lastIdx-1] | |
84 } | |
85 return bstr[:lastIdx-1] + string(last-1) | |
86 } | |
87 } | |
88 | |
89 func (q *queryIneqFilter) constrain(op queryOp, val string) bool { | |
90 switch op { | |
91 case qLessThan: | |
92 val = increment(val, true) | |
93 fallthrough | |
94 case qLessEq: | |
95 // adjust upper bound downwards | |
96 if q.high > val { | |
97 q.high = val | |
98 } | |
99 | |
100 case qGreaterThan: | |
101 val = increment(val, false) | |
102 fallthrough | |
103 case qGreaterEq: | |
104 // adjust lower bound upwards | |
105 if q.low < val { | |
106 q.low = val | |
107 } | |
108 } | |
109 | |
110 return q.low > q.high | |
111 } | |
112 | |
72 type queryImpl struct { | 113 type queryImpl struct { |
73 ns string | 114 ns string |
74 | 115 |
75 kind string | 116 kind string |
76 ancestor ds.Key | 117 ancestor ds.Key |
77 » filter []queryFilter | 118 |
78 » order []ds.IndexColumn | 119 » // prop -> encoded values |
79 » project []string | 120 » eqFilters map[string]map[string]struct{} |
121 » ineqFilter queryIneqFilter | |
122 » order []ds.IndexColumn | |
123 » project map[string]struct{} | |
80 | 124 |
81 distinct bool | 125 distinct bool |
82 eventualConsistency bool | 126 eventualConsistency bool |
83 keysOnly bool | 127 keysOnly bool |
84 limit int32 | 128 limit int32 |
85 offset int32 | 129 offset int32 |
86 | 130 |
87 start queryCursor | 131 start queryCursor |
88 end queryCursor | 132 end queryCursor |
89 | 133 |
90 err error | 134 err error |
91 } | 135 } |
92 | 136 |
93 var _ ds.Query = (*queryImpl)(nil) | 137 var _ ds.Query = (*queryImpl)(nil) |
94 | 138 |
95 func (q *queryImpl) normalize() (ret *queryImpl) { | 139 func (q *queryImpl) valid(ns string, isTxn bool) (done bool, err error) { |
96 » // ported from GAE SDK datastore_index.py;Normalize() | 140 » if q.err == errQueryDone { |
97 » ret = q.clone() | 141 » » done = true |
98 | 142 » } else if q.err != nil { |
99 » bs := newMemStore() | 143 » » err = q.err |
100 | 144 » } else if ns != q.ns { |
101 » eqProperties := bs.MakePrivateCollection(nil) | 145 » » err = errors.New( |
102 | |
103 » ineqProperties := bs.MakePrivateCollection(nil) | |
104 | |
105 » for _, f := range ret.filter { | |
106 » » // if we supported the IN operator, we would check to see if the re were | |
107 » » // multiple value operands here, but the go SDK doesn't support this. | |
108 » » if f.op.isEQOp() { | |
109 » » » eqProperties.Set([]byte(f.prop), []byte{}) | |
110 » » } else if f.op.isINEQOp() { | |
111 » » » ineqProperties.Set([]byte(f.prop), []byte{}) | |
112 » » } | |
113 » } | |
114 | |
115 » ineqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool { | |
116 » » eqProperties.Delete(i.Key) | |
117 » » return true | |
118 » }) | |
119 | |
120 » removeSet := bs.MakePrivateCollection(nil) | |
121 » eqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool { | |
122 » » removeSet.Set(i.Key, []byte{}) | |
123 » » return true | |
124 » }) | |
125 | |
126 » newOrders := []ds.IndexColumn{} | |
127 » for _, o := range ret.order { | |
128 » » if removeSet.Get([]byte(o.Property)) == nil { | |
129 » » » removeSet.Set([]byte(o.Property), []byte{}) | |
130 » » » newOrders = append(newOrders, o) | |
131 » » } | |
132 » } | |
133 » ret.order = newOrders | |
134 | |
135 » // need to fix ret.filters if we ever support the EXISTS operator and/or | |
136 » // projections. | |
137 » // | |
138 » // newFilters = [] | |
139 » // for f in ret.filters: | |
140 » // if f.op != qExists: | |
141 » // newFilters = append(newFilters, f) | |
142 » // if !removeSet.Has(f.prop): | |
143 » // removeSet.InsertNoReplace(f.prop) | |
144 » // newFilters = append(newFilters, f) | |
145 » // | |
146 » // so ret.filters == newFilters becuase none of ret.filters has op == qE xists | |
147 » // | |
148 » // then: | |
149 » // | |
150 » // for prop in ret.project: | |
151 » // if !removeSet.Has(prop): | |
152 » // removeSet.InsertNoReplace(prop) | |
153 » // ... make new EXISTS filters, add them to newFilters ... | |
154 » // ret.filters = newFilters | |
155 » // | |
156 » // However, since we don't support projection queries, this is moot. | |
157 | |
158 » if eqProperties.Get([]byte("__key__")) != nil { | |
159 » » ret.order = []ds.IndexColumn{} | |
160 » } | |
161 | |
162 » newOrders = []ds.IndexColumn{} | |
163 » for _, o := range ret.order { | |
164 » » if o.Property == "__key__" { | |
165 » » » newOrders = append(newOrders, o) | |
166 » » » break | |
167 » » } | |
168 » » newOrders = append(newOrders, o) | |
169 » } | |
170 » ret.order = newOrders | |
171 | |
172 » return | |
173 } | |
174 | |
175 func (q *queryImpl) checkCorrectness(ns string, isTxn bool) (ret *queryImpl) { | |
176 » // ported from GAE SDK datastore_stub_util.py;CheckQuery() | |
177 » ret = q.clone() | |
178 | |
179 » if ns != ret.ns { | |
180 » » ret.err = errors.New( | |
181 "gae/memory: Namespace mismatched. Query and Datastore d on't agree " + | 146 "gae/memory: Namespace mismatched. Query and Datastore d on't agree " + |
182 "on the current namespace") | 147 "on the current namespace") |
183 » » return | 148 » } else if isTxn && q.ancestor == nil { |
184 » } | 149 » » err = errors.New( |
185 | |
186 » if ret.err != nil { | |
187 » » return | |
188 » } | |
189 | |
190 » // if projection && keys_only: | |
191 » // "projection and keys_only cannot both be set" | |
192 | |
193 » // if projection props match /^__.*__$/: | |
194 » // "projections are not supported for the property: %(prop)s" | |
195 | |
196 » if isTxn && ret.ancestor == nil { | |
197 » » ret.err = errors.New( | |
198 "gae/memory: Only ancestor queries are allowed inside tr ansactions") | 150 "gae/memory: Only ancestor queries are allowed inside tr ansactions") |
199 » » return | 151 » } else if q.numComponents() > MaxQueryComponents { |
200 » } | 152 » » err = fmt.Errorf( |
201 | 153 » » » "gae/memory: query is too large. may not have more than "+ |
202 » numComponents := len(ret.filter) + len(ret.order) | 154 » » » » "%d filters + sort orders + ancestor total: had %d", |
203 » if ret.ancestor != nil { | 155 » » » MaxQueryComponents, q.numComponents()) |
204 » » numComponents++ | 156 » } else if len(q.project) == 0 && q.distinct { |
205 » } | 157 » » err = errors.New( |
206 » if numComponents > 100 { | 158 » » » "gae/memory: Distinct() only makes sense on projection q ueries.") |
207 » » ret.err = errors.New( | |
208 » » » "gae/memory: query is too large. may not have more than " + | |
209 » » » » "100 filters + sort orders ancestor total") | |
210 » } | |
211 | |
212 » // if ret.ancestor.appid() != current appid | |
213 » // "query app is x but ancestor app is x" | |
214 » // if ret.ancestor.namespace() != current namespace | |
215 » // "query namespace is x but ancestor namespace is x" | |
216 | |
217 » // if not all(g in orders for g in group_by) | |
218 » // "items in the group by clause must be specified first in the orderin g" | |
219 | |
220 » ineqPropName := "" | |
221 » for _, f := range ret.filter { | |
222 » » if f.prop == "__key__" { | |
223 » » » k, ok := f.value.(ds.Key) | |
224 » » » if !ok { | |
225 » » » » ret.err = errors.New( | |
226 » » » » » "gae/memory: __key__ filter value must b e a Key") | |
227 » » » » return | |
228 » » » } | |
229 » » » if !ds.KeyValid(k, false, globalAppID, q.ns) { | |
230 » » » » // See the comment in queryImpl.Ancestor; basica lly this check | |
231 » » » » // never happens in the real env because the SDK silently swallows | |
232 » » » » // this condition :/ | |
233 » » » » ret.err = ds.ErrInvalidKey | |
234 » » » » return | |
235 » » » } | |
236 » » » if k.Namespace() != ns { | |
237 » » » » ret.err = fmt.Errorf("bad namespace: %q (expecte d %q)", k.Namespace(), ns) | |
238 » » » » return | |
239 » » » } | |
240 » » » // __key__ filter app is X but query app is X | |
241 » » » // __key__ filter namespace is X but query namespace is X | |
242 » » } | |
243 » » // if f.op == qEqual and f.prop in ret.project_fields | |
244 » » // "cannot use projection on a proprety with an equality filte r" | |
245 | |
246 » » if f.op.isINEQOp() { | |
247 » » » if ineqPropName == "" { | |
248 » » » » ineqPropName = f.prop | |
249 » » » } else if f.prop != ineqPropName { | |
250 » » » » ret.err = fmt.Errorf( | |
251 » » » » » "gae/memory: Only one inequality filter per query is supported. "+ | |
252 » » » » » » "Encountered both %s and %s", in eqPropName, f.prop) | |
253 » » » » return | |
254 » » » } | |
255 » » } | |
256 » } | |
257 | |
258 » // if ineqPropName != "" && len(group_by) > 0 && len(orders) ==0 | |
259 » // "Inequality filter on X must also be a group by property "+ | |
260 » // "when group by properties are set." | |
261 | |
262 » if ineqPropName != "" && len(ret.order) != 0 { | |
263 » » if ret.order[0].Property != ineqPropName { | |
264 » » » ret.err = fmt.Errorf( | |
265 » » » » "gae/memory: The first sort property must be the same as the property "+ | |
266 » » » » » "to which the inequality filter is appli ed. In your query "+ | |
267 » » » » » "the first sort property is %s but the i nequality filter "+ | |
268 » » » » » "is on %s", ret.order[0].Property, ineqP ropName) | |
269 » » » return | |
270 » » } | |
271 » } | |
272 | |
273 » if ret.kind == "" { | |
274 » » for _, f := range ret.filter { | |
275 » » » if f.prop != "__key__" { | |
276 » » » » ret.err = errors.New( | |
277 » » » » » "gae/memory: kind is required for non-__ key__ filters") | |
278 » » » » return | |
279 » » » } | |
280 » » } | |
281 » » for _, o := range ret.order { | |
282 » » » if o.Property != "__key__" || o.Direction != ds.ASCENDIN G { | |
283 » » » » ret.err = errors.New( | |
284 » » » » » "gae/memory: kind is required for all or ders except __key__ ascending") | |
285 » » » » return | |
286 » » » } | |
287 » » } | |
288 } | 159 } |
289 return | 160 return |
290 } | 161 } |
291 | 162 |
163 func (q *queryImpl) numComponents() int { | |
164 numComponents := len(q.order) | |
165 if q.ineqFilter.prop != "" { | |
166 numComponents++ | |
167 } | |
168 for _, v := range q.eqFilters { | |
169 numComponents += len(v) | |
170 } | |
171 if q.ancestor != nil { | |
172 numComponents++ | |
173 } | |
174 return numComponents | |
175 } | |
176 | |
292 func (q *queryImpl) calculateIndex() *ds.IndexDefinition { | 177 func (q *queryImpl) calculateIndex() *ds.IndexDefinition { |
293 // as a nod to simplicity in this code, we'll require that a single inde x | 178 // as a nod to simplicity in this code, we'll require that a single inde x |
294 // is able to service the entire query. E.g. no zigzag merge joins or | 179 // is able to service the entire query. E.g. no zigzag merge joins or |
295 // multiqueries. This will mean that the user will need to rely on | 180 // multiqueries. This will mean that the user will need to rely on |
296 // dev_appserver to tell them what indicies they need for real, and for thier | 181 // dev_appserver to tell them what indicies they need for real, and for thier |
297 // tests they'll need to specify the missing composite indices manually. | 182 // tests they'll need to specify the missing composite indices manually. |
298 // | 183 // |
299 // This COULD lead to an exploding indicies problem, but we can fix that when | 184 // This COULD lead to an exploding indicies problem, but we can fix that when |
300 // we get to it. | 185 // we get to it. |
301 | 186 |
302 //sortOrders := []qSortBy{} | 187 //sortOrders := []qSortBy{} |
303 | 188 |
304 return nil | 189 return nil |
305 } | 190 } |
306 | 191 |
307 func (q *queryImpl) clone() *queryImpl { | 192 // checkMutateClone sees if the query has an error. If not, it clones the query, |
308 » ret := *q | 193 // and assigns the output of `check` to the query error slot. If check returns |
309 » ret.filter = append([]queryFilter(nil), q.filter...) | 194 // nil, it calls `mutate` on the cloned query. The (possibly new) query is then |
310 » ret.order = append([]ds.IndexColumn(nil), q.order...) | 195 // returned. |
311 » ret.project = append([]string(nil), q.project...) | 196 func (q *queryImpl) checkMutateClone(check func() error, mutate func(*queryImpl) ) *queryImpl { |
312 » return &ret | 197 » if q.err != nil { |
198 » » return q | |
199 » } | |
200 » nq := *q | |
201 » nq.eqFilters = make(map[string]map[string]struct{}, len(q.eqFilters)) | |
202 » for prop, vals := range q.eqFilters { | |
203 » » nq.eqFilters[prop] = make(map[string]struct{}, len(vals)) | |
204 » » for v := range vals { | |
205 » » » nq.eqFilters[prop][v] = struct{}{} | |
206 » » } | |
207 » } | |
208 » nq.order = append([]ds.IndexColumn(nil), q.order...) | |
dnj (Google)
2015/08/14 21:01:35
I used to like doing this; however, "append" will
iannucci
2015/08/14 22:25:47
done
| |
209 » nq.project = make(map[string]struct{}, len(q.project)) | |
210 » for f := range q.project { | |
211 » » nq.project[f] = struct{}{} | |
212 » } | |
213 » if check != nil { | |
214 » » nq.err = check() | |
215 » } | |
216 » if nq.err == nil { | |
217 » » mutate(&nq) | |
218 » } | |
219 » return &nq | |
313 } | 220 } |
314 | 221 |
315 func (q *queryImpl) Ancestor(k ds.Key) ds.Query { | 222 func (q *queryImpl) Ancestor(k ds.Key) ds.Query { |
316 » q = q.clone() | 223 » return q.checkMutateClone( |
317 » q.ancestor = k | 224 » » func() error { |
318 » if k == nil { | 225 » » » if k == nil { |
319 » » // SDK has an explicit nil-check | 226 » » » » // SDK has an explicit nil-check |
320 » » q.err = errors.New("datastore: nil query ancestor") | 227 » » » » return errors.New("datastore: nil query ancestor ") |
321 » } else if !ds.KeyValid(k, false, globalAppID, q.ns) { | 228 » » » } |
322 » » // technically the SDK implementation does a Weird Thing (tm) if both the | 229 » » » if !ds.KeyValid(k, false, globalAppID, q.ns) { |
323 » » // stringID and intID are set on a key; it only serializes the s tringID in | 230 » » » » // technically the SDK implementation does a Wei rd Thing (tm) if both the |
324 » » // the proto. This means that if you set the Ancestor to an inva lid key, | 231 » » » » // stringID and intID are set on a key; it only serializes the stringID in |
325 » » // you'll never actually hear about it. Instead of doing that in sanity, we | 232 » » » » // the proto. This means that if you set the Anc estor to an invalid key, |
326 » » // just swap to an error here. | 233 » » » » // you'll never actually hear about it. Instead of doing that insanity, we |
327 » » q.err = ds.ErrInvalidKey | 234 » » » » // just swap to an error here. |
328 » } else if k.Namespace() != q.ns { | 235 » » » » return ds.ErrInvalidKey |
329 » » q.err = fmt.Errorf("bad namespace: %q (expected %q)", k.Namespac e(), q.ns) | 236 » » » } |
330 » } | 237 » » » if k.Namespace() != q.ns { |
331 » return q | 238 » » » » return fmt.Errorf("bad namespace: %q (expected % q)", k.Namespace(), q.ns) |
239 » » » } | |
240 » » » if q.ancestor != nil { | |
241 » » » » return errors.New("cannot have more than one anc estor") | |
242 » » » } | |
243 » » » return nil | |
244 » » }, | |
245 » » func(q *queryImpl) { | |
246 » » » q.ancestor = k | |
247 » » }) | |
332 } | 248 } |
333 | 249 |
334 func (q *queryImpl) Distinct() ds.Query { | 250 func (q *queryImpl) Distinct() ds.Query { |
335 » q = q.clone() | 251 » return q.checkMutateClone(nil, func(q *queryImpl) { |
336 » q.distinct = true | 252 » » q.distinct = true |
337 » return q | 253 » }) |
338 } | 254 } |
339 | 255 |
340 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query { | 256 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query { |
341 » q = q.clone() | 257 » prop := "" |
342 » f, err := parseFilter(fStr, val) | 258 » op := qInvalid |
343 » if err != nil { | 259 » binVal := "" |
344 » » q.err = err | 260 » return q.checkMutateClone( |
345 » » return q | 261 » » func() (err error) { |
dnj (Google)
2015/08/14 21:01:35
No point in a named variable that you only use it
iannucci
2015/08/14 22:25:47
*shrug* changed
dnj (Google)
2015/08/14 22:58:13
maruel@ cares :P
| |
346 » } | 262 » » » prop, op, err = parseFilter(fStr) |
347 » q.filter = append(q.filter, f) | 263 » » » if err != nil { |
348 » return q | 264 » » » » return |
265 » » » } | |
266 | |
267 » » » if q.kind == "" && prop != "__key__" { | |
268 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#Go_Kindless_queries | |
269 » » » » return fmt.Errorf( | |
270 » » » » » "kindless queries can only filter on __k ey__, got %q", fStr) | |
271 » » » } | |
272 | |
273 » » » p := ds.Property{} | |
274 » » » err = p.SetValue(val, ds.NoIndex) | |
275 » » » if err != nil { | |
276 » » » » return err | |
dnj (Google)
2015/08/14 21:01:35
If still using named return variables, just "retur
iannucci
2015/08/14 22:25:47
Acknowledged.
| |
277 » » » } | |
278 | |
279 » » » if p.Type() == ds.PTKey { | |
280 » » » » if !ds.KeyValid(p.Value().(ds.Key), false, globa lAppID, q.ns) { | |
281 » » » » » return ds.ErrInvalidKey | |
282 » » » » } | |
283 » » » } | |
284 | |
285 » » » buf := &bytes.Buffer{} | |
dnj (Google)
2015/08/14 21:01:35
Might as well do this down near return statement,
iannucci
2015/08/14 22:25:47
Done.
| |
286 » » » p.Write(buf, ds.WithoutContext) | |
287 | |
288 » » » if prop == "__key__" { | |
289 » » » » if op == qEqual { | |
290 » » » » » return fmt.Errorf( | |
291 » » » » » » "query equality filter on __key_ _ is silly: %q", fStr) | |
292 » » » » } | |
293 » » » » if p.Type() != ds.PTKey { | |
294 » » » » » return fmt.Errorf("__key__ filter value is not a key: %T", val) | |
295 » » » » } | |
296 » » » } | |
297 | |
298 » » » if op != qEqual { | |
299 » » » » if q.ineqFilter.prop != "" && q.ineqFilter.prop != prop { | |
300 » » » » » return fmt.Errorf( | |
301 » » » » » » "inequality filters on multiple properties: %q and %q", | |
302 » » » » » » q.ineqFilter.prop, prop) | |
303 » » » » } | |
304 » » » » if len(q.order) > 0 && q.order[0].Property != pr op { | |
305 » » » » » return fmt.Errorf( | |
306 » » » » » » "first sort order must match ine quality filter: %q v %q", | |
307 » » » » » » q.order[0].Property, prop) | |
308 » » » » } | |
309 » » » } else if _, ok := q.project[prop]; ok { | |
310 » » » » return fmt.Errorf( | |
311 » » » » » "cannot project on field which is used i n an equality filter: %q", | |
312 » » » » » prop) | |
313 » » » } | |
314 » » » binVal = buf.String() | |
315 » » » return | |
316 » » }, | |
317 » » func(q *queryImpl) { | |
318 » » » if op == qEqual { | |
319 » » » » // add it to eq filters | |
320 » » » » if _, ok := q.eqFilters[prop]; !ok { | |
321 » » » » » q.eqFilters[prop] = map[string]struct{}{ binVal: {}} | |
322 » » » » } else { | |
323 » » » » » q.eqFilters[prop][binVal] = struct{}{} | |
324 » » » » } | |
325 | |
326 » » » » // remove it from sort orders. | |
327 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters | |
328 » » » » toRm := -1 | |
329 » » » » for i, o := range q.order { | |
330 » » » » » if o.Property == prop { | |
331 » » » » » » toRm = i | |
332 » » » » » » break | |
333 » » » » » } | |
334 » » » » } | |
335 » » » » if toRm >= 0 { | |
336 » » » » » q.order = append(q.order[:toRm], q.order [toRm+1:]...) | |
337 » » » » } | |
338 » » » } else { | |
339 » » » » q.ineqFilter.prop = prop | |
340 » » » » if !q.ineqFilter.constrain(op, binVal) { | |
341 » » » » » q.err = errQueryDone | |
342 » » » » } | |
343 » » » } | |
344 » » }) | |
349 } | 345 } |
350 | 346 |
351 func (q *queryImpl) Order(prop string) ds.Query { | 347 func (q *queryImpl) Order(prop string) ds.Query { |
352 » q = q.clone() | 348 » col := ds.IndexColumn{} |
353 » prop = strings.TrimSpace(prop) | 349 » return q.checkMutateClone( |
354 » o := ds.IndexColumn{Property: prop} | 350 » » func() error { |
355 » if strings.HasPrefix(prop, "-") { | 351 » » » // check that first order == first inequality. |
356 » » o.Direction = ds.DESCENDING | 352 » » » // if order is an equality already, ignore it |
357 » » o.Property = strings.TrimSpace(prop[1:]) | 353 » » » col.Property = strings.TrimSpace(prop) |
358 » } else if strings.HasPrefix(prop, "+") { | 354 » » » if strings.HasPrefix(prop, "-") { |
359 » » q.err = fmt.Errorf("datastore: invalid order: %q", prop) | 355 » » » » col.Direction = ds.DESCENDING |
360 » » return q | 356 » » » » col.Property = strings.TrimSpace(prop[1:]) |
361 » } | 357 » » » } else if strings.HasPrefix(prop, "+") { |
dnj (Google)
2015/08/14 21:01:35
Why specifically "+"? Just catch a common user err
iannucci
2015/08/14 22:25:47
this was copied from the sdk.
| |
362 » if len(o.Property) == 0 { | 358 » » » » return fmt.Errorf("datastore: invalid order: %q" , prop) |
363 » » q.err = errors.New("datastore: empty order") | 359 » » » } |
364 » » return q | 360 » » » if len(col.Property) == 0 { |
365 » } | 361 » » » » return errors.New("datastore: empty order") |
366 » q.order = append(q.order, o) | 362 » » » } |
367 » return q | 363 » » » if q.ineqFilter.prop != "" && q.ineqFilter.prop != col.P roperty { |
364 » » » » return fmt.Errorf( | |
365 » » » » » "first sort order must match inequality filter: %q v %q", | |
366 » » » » » prop, q.ineqFilter.prop) | |
367 » » » } | |
368 » » » if q.kind == "" && (col.Property != "__key__" || col.Dir ection != ds.ASCENDING) { | |
369 » » » » return fmt.Errorf("invalid order for kindless qu ery: %#v", col) | |
370 » » » } | |
371 » » » return nil | |
372 » » }, | |
373 » » func(q *queryImpl) { | |
374 » » » if _, ok := q.eqFilters[col.Property]; ok { | |
375 » » » » // skip it if it's an equality filter | |
376 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters | |
377 » » » » return | |
378 » » » } | |
379 » » » for _, order := range q.order { | |
380 » » » » if order.Property == col.Property { | |
381 » » » » » // can't sort by the same order twice | |
382 » » » » » return | |
383 » » » » } | |
384 » » » } | |
385 » » » if col.Property == "__key__" { | |
386 » » » » // __key__ order dominates all other orders | |
387 » » » » q.order = []ds.IndexColumn{col} | |
388 » » » } else { | |
389 » » » » q.order = append(q.order, col) | |
390 » » » } | |
391 » » }) | |
368 } | 392 } |
369 | 393 |
370 func (q *queryImpl) Project(fieldName ...string) ds.Query { | 394 func (q *queryImpl) Project(fieldName ...string) ds.Query { |
371 » q = q.clone() | 395 » return q.checkMutateClone( |
372 » q.project = append(q.project, fieldName...) | 396 » » func() error { |
dnj (Google)
2015/08/14 21:01:35
Test for duplicate project clauses, or is that coo
iannucci
2015/08/14 22:25:47
they're fine, they're just ignored. order doesn't
| |
373 » return q | 397 » » » if q.keysOnly { |
398 » » » » return errors.New("cannot project a keysOnly que ry") | |
399 » » » } | |
400 » » » for _, f := range fieldName { | |
401 » » » » if f == "" { | |
402 » » » » » return errors.New("cannot project on an empty field name") | |
403 » » » » } | |
404 » » » » if strings.HasPrefix(f, "__") && strings.HasSuff ix(f, "__") { | |
405 » » » » » return fmt.Errorf("cannot project on %q" , f) | |
406 » » » » } | |
407 » » » » if _, ok := q.eqFilters[f]; ok { | |
408 » » » » » return fmt.Errorf( | |
409 » » » » » » "cannot project on field which i s used in an equality filter: %q", f) | |
410 » » » » } | |
411 » » » } | |
412 » » » return nil | |
413 » » }, | |
414 » » func(q *queryImpl) { | |
415 » » » for _, f := range fieldName { | |
416 » » » » q.project[f] = struct{}{} | |
417 » » » } | |
418 » » }) | |
374 } | 419 } |
375 | 420 |
376 func (q *queryImpl) KeysOnly() ds.Query { | 421 func (q *queryImpl) KeysOnly() ds.Query { |
377 » q = q.clone() | 422 » return q.checkMutateClone( |
378 » q.keysOnly = true | 423 » » func() error { |
379 » return q | 424 » » » if len(q.project) != 0 { |
425 » » » » return errors.New("cannot project a keysOnly que ry") | |
426 » » » } | |
427 » » » return nil | |
428 » » }, | |
429 » » func(q *queryImpl) { | |
430 » » » q.keysOnly = true | |
431 » » }) | |
380 } | 432 } |
381 | 433 |
382 func (q *queryImpl) Limit(limit int) ds.Query { | 434 func (q *queryImpl) Limit(limit int) ds.Query { |
383 » q = q.clone() | 435 » return q.checkMutateClone( |
384 » if limit < math.MinInt32 || limit > math.MaxInt32 { | 436 » » func() error { |
385 » » q.err = errors.New("datastore: query limit overflow") | 437 » » » if limit < math.MinInt32 || limit > math.MaxInt32 { |
386 » » return q | 438 » » » » return errors.New("datastore: query limit overfl ow") |
387 » } | 439 » » » } |
388 » q.limit = int32(limit) | 440 » » » return nil |
389 » return q | 441 » » }, |
442 » » func(q *queryImpl) { | |
443 » » » q.limit = int32(limit) | |
444 » » }) | |
390 } | 445 } |
391 | 446 |
392 func (q *queryImpl) Offset(offset int) ds.Query { | 447 func (q *queryImpl) Offset(offset int) ds.Query { |
393 » q = q.clone() | 448 » return q.checkMutateClone( |
394 » if offset < 0 { | 449 » » func() error { |
395 » » q.err = errors.New("datastore: negative query offset") | 450 » » » if offset < 0 { |
396 » » return q | 451 » » » » return errors.New("datastore: negative query off set") |
397 » } | 452 » » » } |
398 » if offset > math.MaxInt32 { | 453 » » » if offset > math.MaxInt32 { |
399 » » q.err = errors.New("datastore: query offset overflow") | 454 » » » » return errors.New("datastore: query offset overf low") |
400 » » return q | 455 » » » } |
401 » } | 456 » » » return nil |
402 » q.offset = int32(offset) | 457 » » }, |
403 » return q | 458 » » func(q *queryImpl) { |
459 » » » q.offset = int32(offset) | |
460 » » }) | |
404 } | 461 } |
405 | 462 |
406 func (q *queryImpl) Start(c ds.Cursor) ds.Query { | 463 func (q *queryImpl) Start(c ds.Cursor) ds.Query { |
407 » q = q.clone() | 464 » curs := queryCursor("") |
408 » curs := c.(queryCursor) | 465 » return q.checkMutateClone( |
409 » if !curs.Valid() { | 466 » » func() error { |
410 » » q.err = errors.New("datastore: invalid cursor") | 467 » » » ok := false |
411 » » return q | 468 » » » if curs, ok = c.(queryCursor); !ok { |
412 » } | 469 » » » » return fmt.Errorf("start cursor is unknown type: %T", c) |
413 » q.start = curs | 470 » » » } |
414 » return q | 471 » » » if !curs.Valid() { |
472 » » » » return errors.New("datastore: invalid cursor") | |
473 » » » } | |
474 » » » return nil | |
475 » » }, | |
476 » » func(q *queryImpl) { | |
477 » » » q.start = curs | |
478 » » }) | |
415 } | 479 } |
416 | 480 |
417 func (q *queryImpl) End(c ds.Cursor) ds.Query { | 481 func (q *queryImpl) End(c ds.Cursor) ds.Query { |
418 » q = q.clone() | 482 » curs := queryCursor("") |
419 » curs := c.(queryCursor) | 483 » return q.checkMutateClone( |
420 » if !curs.Valid() { | 484 » » func() error { |
421 » » q.err = errors.New("datastore: invalid cursor") | 485 » » » ok := false |
422 » » return q | 486 » » » if curs, ok = c.(queryCursor); !ok { |
423 » } | 487 » » » » return fmt.Errorf("end cursor is unknown type: % T", c) |
424 » q.end = curs | 488 » » » } |
425 » return q | 489 » » » if !curs.Valid() { |
490 » » » » return errors.New("datastore: invalid cursor") | |
491 » » » } | |
492 » » » return nil | |
493 » » }, | |
494 » » func(q *queryImpl) { | |
495 » » » q.end = curs | |
496 » » }) | |
426 } | 497 } |
427 | 498 |
428 func (q *queryImpl) EventualConsistency() ds.Query { | 499 func (q *queryImpl) EventualConsistency() ds.Query { |
429 » q = q.clone() | 500 » return q.checkMutateClone( |
430 » q.eventualConsistency = true | 501 » » nil, func(q *queryImpl) { |
431 » return q | 502 » » » q.eventualConsistency = true |
432 } | 503 » » }) |
504 } | |
OLD | NEW |