Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Side by Side Diff: impl/memory/datastore_query.go

Issue 1286943002: impl/memory: Make queries self-validate (Closed) Base URL: https://github.com/luci/gae.git@add_datastore_testable
Patch Set: add doc Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698