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

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: rebase 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
« no previous file with comments | « impl/memory/datastore_index.go ('k') | impl/memory/datastore_query_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 // MaxQueryComponents was lifted from a hard-coded constant in dev_appserver.
18 // No idea if it's a real limit or just a convenience in the current dev
19 // appserver implementation.
20 const MaxQueryComponents = 100
21
22 var errQueryDone = errors.New("query is done")
23
17 type queryOp int 24 type queryOp int
18 25
19 const ( 26 const (
20 qInvalid queryOp = iota 27 qInvalid queryOp = iota
21 qEqual 28 qEqual
22 qLessThan 29 qLessThan
23 qLessEq 30 qLessEq
24 qGreaterEq 31 qGreaterEq
25 qGreaterThan 32 qGreaterThan
26 ) 33 )
27 34
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{ 35 var queryOpMap = map[string]queryOp{
37 "=": qEqual, 36 "=": qEqual,
38 "<": qLessThan, 37 "<": qLessThan,
39 "<=": qLessEq, 38 "<=": qLessEq,
40 ">=": qGreaterEq, 39 ">=": qGreaterEq,
41 ">": qGreaterThan, 40 ">": qGreaterThan,
42 } 41 }
43 42
44 type queryFilter struct { 43 type queryFilter struct {
45 prop string 44 prop string
46 op queryOp 45 op queryOp
47 value interface{} 46 value interface{}
48 } 47 }
49 48
50 func parseFilter(f string, v interface{}) (ret queryFilter, err error) { 49 func parseFilter(f string) (prop string, op queryOp, err error) {
51 toks := strings.SplitN(strings.TrimSpace(f), " ", 2) 50 toks := strings.SplitN(strings.TrimSpace(f), " ", 2)
52 if len(toks) != 2 { 51 if len(toks) != 2 {
53 err = errors.New("datastore: invalid filter: " + f) 52 err = errors.New("datastore: invalid filter: " + f)
54 } else { 53 } else {
55 » » op := queryOpMap[toks[1]] 54 » » op = queryOpMap[toks[1]]
56 if op == qInvalid { 55 if op == qInvalid {
57 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f) 56 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f)
58 } else { 57 } else {
59 » » » ret.prop = toks[0] 58 » » » prop = toks[0]
60 » » » ret.op = op
61 » » » ret.value = v
62 } 59 }
63 } 60 }
64 return 61 return
65 } 62 }
66 63
67 type queryCursor string 64 type queryCursor string
68 65
69 func (q queryCursor) String() string { return string(q) } 66 func (q queryCursor) String() string { return string(q) }
70 func (q queryCursor) Valid() bool { return q != "" } 67 func (q queryCursor) Valid() bool { return q != "" }
71 68
69 type queryIneqFilter struct {
70 prop string
71
72 low *string
73 high *string
74 }
75
76 func increment(bstr string, positive bool) string {
77 lastIdx := len(bstr) - 1
78 last := bstr[lastIdx]
79 if positive {
80 if last == 0xFF {
81 return bstr + "\x00"
82 }
83 return bstr[:lastIdx-1] + string(last+1)
84 } else {
85 if last == 0 {
86 return bstr[:lastIdx-1]
87 }
88 return bstr[:lastIdx-1] + string(last-1)
89 }
90 }
91
92 // constrain 'folds' a new inequality into the current inequality filter.
93 //
94 // It will bump the high bound down, or the low bound up, assuming the incoming
95 // constraint does so.
96 //
97 // It returns true iff the filter is overconstrained (i.e. low > high)
98 func (q *queryIneqFilter) constrain(op queryOp, val string) bool {
99 switch op {
100 case qLessThan:
101 val = increment(val, true)
102 fallthrough
103 case qLessEq:
104 // adjust upper bound downwards
105 if q.high == nil || *q.high > val {
106 q.high = &val
107 }
108
109 case qGreaterThan:
110 val = increment(val, false)
111 fallthrough
112 case qGreaterEq:
113 // adjust lower bound upwards
114 if q.low == nil || *q.low < val {
115 q.low = &val
116 }
117
118 default:
119 panic(fmt.Errorf("constrain cannot handle filter op %d", op))
120 }
121
122 if q.low != nil && q.high != nil {
123 return *q.low > *q.high
124 }
125 return false
126 }
127
72 type queryImpl struct { 128 type queryImpl struct {
73 ns string 129 ns string
74 130
75 kind string 131 kind string
76 ancestor ds.Key 132 ancestor ds.Key
77 » filter []queryFilter 133
78 » order []ds.IndexColumn 134 » // prop -> encoded values
79 » project []string 135 » eqFilters map[string]map[string]struct{}
136 » ineqFilter queryIneqFilter
137 » order []ds.IndexColumn
138 » project map[string]struct{}
80 139
81 distinct bool 140 distinct bool
82 eventualConsistency bool 141 eventualConsistency bool
83 keysOnly bool 142 keysOnly bool
84 limit int32 143 limit int32
85 offset int32 144 offset int32
86 145
87 start queryCursor 146 start queryCursor
88 end queryCursor 147 end queryCursor
89 148
90 err error 149 err error
91 } 150 }
92 151
93 var _ ds.Query = (*queryImpl)(nil) 152 var _ ds.Query = (*queryImpl)(nil)
94 153
95 func (q *queryImpl) normalize() (ret *queryImpl) { 154 func (q *queryImpl) valid(ns string, isTxn bool) (done bool, err error) {
96 » // ported from GAE SDK datastore_index.py;Normalize() 155 » if q.err == errQueryDone {
97 » ret = q.clone() 156 » » done = true
98 157 » } else if q.err != nil {
99 » bs := newMemStore() 158 » » err = q.err
100 159 » } else if ns != q.ns {
101 » eqProperties := bs.MakePrivateCollection(nil) 160 » » 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 " + 161 "gae/memory: Namespace mismatched. Query and Datastore d on't agree " +
182 "on the current namespace") 162 "on the current namespace")
183 » » return 163 » } else if isTxn && q.ancestor == nil {
184 » } 164 » » 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") 165 "gae/memory: Only ancestor queries are allowed inside tr ansactions")
199 » » return 166 » } else if q.numComponents() > MaxQueryComponents {
200 » } 167 » » err = fmt.Errorf(
201 168 » » » "gae/memory: query is too large. may not have more than "+
202 » numComponents := len(ret.filter) + len(ret.order) 169 » » » » "%d filters + sort orders + ancestor total: had %d",
203 » if ret.ancestor != nil { 170 » » » MaxQueryComponents, q.numComponents())
204 » » numComponents++ 171 » } else if len(q.project) == 0 && q.distinct {
205 » } 172 » » // This must be delayed, because q.Distinct().Project("foo") is a valid
206 » if numComponents > 100 { 173 » » // construction. If we checked this in Distinct, it could be too early, and
207 » » ret.err = errors.New( 174 » » // checking it in Project doesn't matter.
208 » » » "gae/memory: query is too large. may not have more than " + 175 » » err = errors.New(
209 » » » » "100 filters + sort orders ancestor total") 176 » » » "gae/memory: Distinct() only makes sense on projection q ueries.")
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 } 177 }
289 return 178 return
290 } 179 }
291 180
181 func (q *queryImpl) numComponents() int {
182 numComponents := len(q.order)
183 if q.ineqFilter.prop != "" {
184 if q.ineqFilter.low != nil {
185 numComponents++
186 }
187 if q.ineqFilter.high != nil {
188 numComponents++
189 }
190 }
191 for _, v := range q.eqFilters {
192 numComponents += len(v)
193 }
194 if q.ancestor != nil {
195 numComponents++
196 }
197 return numComponents
198 }
199
292 func (q *queryImpl) calculateIndex() *ds.IndexDefinition { 200 func (q *queryImpl) calculateIndex() *ds.IndexDefinition {
293 // as a nod to simplicity in this code, we'll require that a single inde x 201 // 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 202 // 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 203 // 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 204 // 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. 205 // tests they'll need to specify the missing composite indices manually.
298 // 206 //
299 // This COULD lead to an exploding indicies problem, but we can fix that when 207 // This COULD lead to an exploding indicies problem, but we can fix that when
300 // we get to it. 208 // we get to it.
301 209
302 //sortOrders := []qSortBy{} 210 //sortOrders := []qSortBy{}
303 211
304 return nil 212 return nil
305 } 213 }
306 214
307 func (q *queryImpl) clone() *queryImpl { 215 // checkMutateClone sees if the query has an error. If not, it clones the query,
308 » ret := *q 216 // and assigns the output of `check` to the query error slot. If check returns
309 » ret.filter = append([]queryFilter(nil), q.filter...) 217 // nil, it calls `mutate` on the cloned query. The (possibly new) query is then
310 » ret.order = append([]ds.IndexColumn(nil), q.order...) 218 // returned.
311 » ret.project = append([]string(nil), q.project...) 219 func (q *queryImpl) checkMutateClone(check func() error, mutate func(*queryImpl) ) *queryImpl {
312 » return &ret 220 » if q.err != nil {
221 » » return q
222 » }
223 » nq := *q
224 » nq.eqFilters = make(map[string]map[string]struct{}, len(q.eqFilters))
225 » for prop, vals := range q.eqFilters {
226 » » nq.eqFilters[prop] = make(map[string]struct{}, len(vals))
227 » » for v := range vals {
228 » » » nq.eqFilters[prop][v] = struct{}{}
229 » » }
230 » }
231 » nq.order = make([]ds.IndexColumn, len(q.order))
232 » copy(nq.order, q.order)
233 » nq.project = make(map[string]struct{}, len(q.project))
234 » for f := range q.project {
235 » » nq.project[f] = struct{}{}
236 » }
237 » if check != nil {
238 » » nq.err = check()
239 » }
240 » if nq.err == nil {
241 » » mutate(&nq)
242 » }
243 » return &nq
313 } 244 }
314 245
315 func (q *queryImpl) Ancestor(k ds.Key) ds.Query { 246 func (q *queryImpl) Ancestor(k ds.Key) ds.Query {
316 » q = q.clone() 247 » return q.checkMutateClone(
317 » q.ancestor = k 248 » » func() error {
318 » if k == nil { 249 » » » if k == nil {
319 » » // SDK has an explicit nil-check 250 » » » » // SDK has an explicit nil-check
320 » » q.err = errors.New("datastore: nil query ancestor") 251 » » » » return errors.New("datastore: nil query ancestor ")
321 » } else if !ds.KeyValid(k, false, globalAppID, q.ns) { 252 » » » }
322 » » // technically the SDK implementation does a Weird Thing (tm) if both the 253 » » » if !ds.KeyValid(k, false, globalAppID, q.ns) {
323 » » // stringID and intID are set on a key; it only serializes the s tringID in 254 » » » » // 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, 255 » » » » // 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 256 » » » » // the proto. This means that if you set the Anc estor to an invalid key,
326 » » // just swap to an error here. 257 » » » » // you'll never actually hear about it. Instead of doing that insanity, we
327 » » q.err = ds.ErrInvalidKey 258 » » » » // just swap to an error here.
328 » } else if k.Namespace() != q.ns { 259 » » » » return ds.ErrInvalidKey
329 » » q.err = fmt.Errorf("bad namespace: %q (expected %q)", k.Namespac e(), q.ns) 260 » » » }
330 » } 261 » » » if k.Namespace() != q.ns {
331 » return q 262 » » » » return fmt.Errorf("bad namespace: %q (expected % q)", k.Namespace(), q.ns)
263 » » » }
264 » » » if q.ancestor != nil {
265 » » » » return errors.New("cannot have more than one anc estor")
266 » » » }
267 » » » return nil
268 » » },
269 » » func(q *queryImpl) {
270 » » » q.ancestor = k
271 » » })
332 } 272 }
333 273
334 func (q *queryImpl) Distinct() ds.Query { 274 func (q *queryImpl) Distinct() ds.Query {
335 » q = q.clone() 275 » return q.checkMutateClone(nil, func(q *queryImpl) {
336 » q.distinct = true 276 » » q.distinct = true
337 » return q 277 » })
338 } 278 }
339 279
340 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query { 280 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query {
341 » q = q.clone() 281 » prop := ""
342 » f, err := parseFilter(fStr, val) 282 » op := qInvalid
343 » if err != nil { 283 » binVal := ""
344 » » q.err = err 284 » return q.checkMutateClone(
345 » » return q 285 » » func() error {
346 » } 286 » » » var err error
347 » q.filter = append(q.filter, f) 287 » » » prop, op, err = parseFilter(fStr)
348 » return q 288 » » » if err != nil {
289 » » » » return err
290 » » » }
291
292 » » » if q.kind == "" && prop != "__key__" {
293 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#Go_Kindless_queries
294 » » » » return fmt.Errorf(
295 » » » » » "kindless queries can only filter on __k ey__, got %q", fStr)
296 » » » }
297
298 » » » p := ds.Property{}
299 » » » err = p.SetValue(val, ds.NoIndex)
300 » » » if err != nil {
301 » » » » return err
302 » » » }
303
304 » » » if p.Type() == ds.PTKey {
305 » » » » if !ds.KeyValid(p.Value().(ds.Key), false, globa lAppID, q.ns) {
306 » » » » » return ds.ErrInvalidKey
307 » » » » }
308 » » » }
309
310 » » » if prop == "__key__" {
311 » » » » if op == qEqual {
312 » » » » » return fmt.Errorf(
313 » » » » » » "query equality filter on __key_ _ is silly: %q", fStr)
314 » » » » }
315 » » » » if p.Type() != ds.PTKey {
316 » » » » » return fmt.Errorf("__key__ filter value is not a key: %T", val)
317 » » » » }
318 » » » }
319
320 » » » if op != qEqual {
321 » » » » if q.ineqFilter.prop != "" && q.ineqFilter.prop != prop {
322 » » » » » return fmt.Errorf(
323 » » » » » » "inequality filters on multiple properties: %q and %q",
324 » » » » » » q.ineqFilter.prop, prop)
325 » » » » }
326 » » » » if len(q.order) > 0 && q.order[0].Property != pr op {
327 » » » » » return fmt.Errorf(
328 » » » » » » "first sort order must match ine quality filter: %q v %q",
329 » » » » » » q.order[0].Property, prop)
330 » » » » }
331 » » » } else if _, ok := q.project[prop]; ok {
332 » » » » return fmt.Errorf(
333 » » » » » "cannot project on field which is used i n an equality filter: %q",
334 » » » » » prop)
335 » » » }
336
337 » » » buf := &bytes.Buffer{}
338 » » » p.Write(buf, ds.WithoutContext)
339 » » » binVal = buf.String()
340 » » » return nil
341 » » },
342 » » func(q *queryImpl) {
343 » » » if op == qEqual {
344 » » » » // add it to eq filters
345 » » » » if _, ok := q.eqFilters[prop]; !ok {
346 » » » » » q.eqFilters[prop] = map[string]struct{}{ binVal: {}}
347 » » » » } else {
348 » » » » » q.eqFilters[prop][binVal] = struct{}{}
349 » » » » }
350
351 » » » » // remove it from sort orders.
352 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters
353 » » » » toRm := -1
354 » » » » for i, o := range q.order {
355 » » » » » if o.Property == prop {
356 » » » » » » toRm = i
357 » » » » » » break
358 » » » » » }
359 » » » » }
360 » » » » if toRm >= 0 {
361 » » » » » q.order = append(q.order[:toRm], q.order [toRm+1:]...)
362 » » » » }
363 » » » } else {
364 » » » » q.ineqFilter.prop = prop
365 » » » » if q.ineqFilter.constrain(op, binVal) {
366 » » » » » q.err = errQueryDone
367 » » » » }
368 » » » }
369 » » })
349 } 370 }
350 371
351 func (q *queryImpl) Order(prop string) ds.Query { 372 func (q *queryImpl) Order(prop string) ds.Query {
352 » q = q.clone() 373 » col := ds.IndexColumn{}
353 » prop = strings.TrimSpace(prop) 374 » return q.checkMutateClone(
354 » o := ds.IndexColumn{Property: prop} 375 » » func() error {
355 » if strings.HasPrefix(prop, "-") { 376 » » » // check that first order == first inequality.
356 » » o.Direction = ds.DESCENDING 377 » » » // if order is an equality already, ignore it
357 » » o.Property = strings.TrimSpace(prop[1:]) 378 » » » col.Property = strings.TrimSpace(prop)
358 » } else if strings.HasPrefix(prop, "+") { 379 » » » if strings.HasPrefix(prop, "-") {
359 » » q.err = fmt.Errorf("datastore: invalid order: %q", prop) 380 » » » » col.Direction = ds.DESCENDING
360 » » return q 381 » » » » col.Property = strings.TrimSpace(prop[1:])
361 » } 382 » » » } else if strings.HasPrefix(prop, "+") {
362 » if len(o.Property) == 0 { 383 » » » » return fmt.Errorf("datastore: invalid order: %q" , prop)
363 » » q.err = errors.New("datastore: empty order") 384 » » » }
364 » » return q 385 » » » if len(col.Property) == 0 {
365 » } 386 » » » » return errors.New("datastore: empty order")
366 » q.order = append(q.order, o) 387 » » » }
367 » return q 388 » » » if q.ineqFilter.prop != "" && q.ineqFilter.prop != col.P roperty {
389 » » » » return fmt.Errorf(
390 » » » » » "first sort order must match inequality filter: %q v %q",
391 » » » » » prop, q.ineqFilter.prop)
392 » » » }
393 » » » if q.kind == "" && (col.Property != "__key__" || col.Dir ection != ds.ASCENDING) {
394 » » » » return fmt.Errorf("invalid order for kindless qu ery: %#v", col)
395 » » » }
396 » » » return nil
397 » » },
398 » » func(q *queryImpl) {
399 » » » if _, ok := q.eqFilters[col.Property]; ok {
400 » » » » // skip it if it's an equality filter
401 » » » » // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters
402 » » » » return
403 » » » }
404 » » » for _, order := range q.order {
405 » » » » if order.Property == col.Property {
406 » » » » » // can't sort by the same order twice
407 » » » » » return
408 » » » » }
409 » » » }
410 » » » if col.Property == "__key__" {
411 » » » » // __key__ order dominates all other orders
412 » » » » q.order = []ds.IndexColumn{col}
413 » » » } else {
414 » » » » q.order = append(q.order, col)
415 » » » }
416 » » })
368 } 417 }
369 418
370 func (q *queryImpl) Project(fieldName ...string) ds.Query { 419 func (q *queryImpl) Project(fieldName ...string) ds.Query {
371 » q = q.clone() 420 » return q.checkMutateClone(
372 » q.project = append(q.project, fieldName...) 421 » » func() error {
373 » return q 422 » » » if q.keysOnly {
423 » » » » return errors.New("cannot project a keysOnly que ry")
424 » » » }
425 » » » for _, f := range fieldName {
426 » » » » if f == "" {
427 » » » » » return errors.New("cannot project on an empty field name")
428 » » » » }
429 » » » » if strings.HasPrefix(f, "__") && strings.HasSuff ix(f, "__") {
430 » » » » » return fmt.Errorf("cannot project on %q" , f)
431 » » » » }
432 » » » » if _, ok := q.eqFilters[f]; ok {
433 » » » » » return fmt.Errorf(
434 » » » » » » "cannot project on field which i s used in an equality filter: %q", f)
435 » » » » }
436 » » » }
437 » » » return nil
438 » » },
439 » » func(q *queryImpl) {
440 » » » for _, f := range fieldName {
441 » » » » q.project[f] = struct{}{}
442 » » » }
443 » » })
374 } 444 }
375 445
376 func (q *queryImpl) KeysOnly() ds.Query { 446 func (q *queryImpl) KeysOnly() ds.Query {
377 » q = q.clone() 447 » return q.checkMutateClone(
378 » q.keysOnly = true 448 » » func() error {
379 » return q 449 » » » if len(q.project) != 0 {
450 » » » » return errors.New("cannot project a keysOnly que ry")
451 » » » }
452 » » » return nil
453 » » },
454 » » func(q *queryImpl) {
455 » » » q.keysOnly = true
456 » » })
380 } 457 }
381 458
382 func (q *queryImpl) Limit(limit int) ds.Query { 459 func (q *queryImpl) Limit(limit int) ds.Query {
383 » q = q.clone() 460 » return q.checkMutateClone(
384 » if limit < math.MinInt32 || limit > math.MaxInt32 { 461 » » func() error {
385 » » q.err = errors.New("datastore: query limit overflow") 462 » » » if limit < math.MinInt32 || limit > math.MaxInt32 {
386 » » return q 463 » » » » return errors.New("datastore: query limit overfl ow")
387 » } 464 » » » }
388 » q.limit = int32(limit) 465 » » » return nil
389 » return q 466 » » },
467 » » func(q *queryImpl) {
468 » » » q.limit = int32(limit)
469 » » })
390 } 470 }
391 471
392 func (q *queryImpl) Offset(offset int) ds.Query { 472 func (q *queryImpl) Offset(offset int) ds.Query {
393 » q = q.clone() 473 » return q.checkMutateClone(
394 » if offset < 0 { 474 » » func() error {
395 » » q.err = errors.New("datastore: negative query offset") 475 » » » if offset < 0 {
396 » » return q 476 » » » » return errors.New("datastore: negative query off set")
397 » } 477 » » » }
398 » if offset > math.MaxInt32 { 478 » » » if offset > math.MaxInt32 {
399 » » q.err = errors.New("datastore: query offset overflow") 479 » » » » return errors.New("datastore: query offset overf low")
400 » » return q 480 » » » }
401 » } 481 » » » return nil
402 » q.offset = int32(offset) 482 » » },
403 » return q 483 » » func(q *queryImpl) {
484 » » » q.offset = int32(offset)
485 » » })
404 } 486 }
405 487
406 func (q *queryImpl) Start(c ds.Cursor) ds.Query { 488 func (q *queryImpl) Start(c ds.Cursor) ds.Query {
407 » q = q.clone() 489 » curs := queryCursor("")
408 » curs := c.(queryCursor) 490 » return q.checkMutateClone(
409 » if !curs.Valid() { 491 » » func() error {
410 » » q.err = errors.New("datastore: invalid cursor") 492 » » » ok := false
411 » » return q 493 » » » if curs, ok = c.(queryCursor); !ok {
412 » } 494 » » » » return fmt.Errorf("start cursor is unknown type: %T", c)
413 » q.start = curs 495 » » » }
414 » return q 496 » » » if !curs.Valid() {
497 » » » » return errors.New("datastore: invalid cursor")
498 » » » }
499 » » » return nil
500 » » },
501 » » func(q *queryImpl) {
502 » » » q.start = curs
503 » » })
415 } 504 }
416 505
417 func (q *queryImpl) End(c ds.Cursor) ds.Query { 506 func (q *queryImpl) End(c ds.Cursor) ds.Query {
418 » q = q.clone() 507 » curs := queryCursor("")
419 » curs := c.(queryCursor) 508 » return q.checkMutateClone(
420 » if !curs.Valid() { 509 » » func() error {
421 » » q.err = errors.New("datastore: invalid cursor") 510 » » » ok := false
422 » » return q 511 » » » if curs, ok = c.(queryCursor); !ok {
423 » } 512 » » » » return fmt.Errorf("end cursor is unknown type: % T", c)
424 » q.end = curs 513 » » » }
425 » return q 514 » » » if !curs.Valid() {
515 » » » » return errors.New("datastore: invalid cursor")
516 » » » }
517 » » » return nil
518 » » },
519 » » func(q *queryImpl) {
520 » » » q.end = curs
521 » » })
426 } 522 }
427 523
428 func (q *queryImpl) EventualConsistency() ds.Query { 524 func (q *queryImpl) EventualConsistency() ds.Query {
429 » q = q.clone() 525 » return q.checkMutateClone(
430 » q.eventualConsistency = true 526 » » nil, func(q *queryImpl) {
431 » return q 527 » » » q.eventualConsistency = true
432 } 528 » » })
529 }
OLDNEW
« no previous file with comments | « impl/memory/datastore_index.go ('k') | impl/memory/datastore_query_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698