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

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

Issue 1302813003: impl/memory: Implement Queries (Closed) Base URL: https://github.com/luci/gae.git@add_multi_iterator
Patch Set: 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"
9 "encoding/base64"
8 "errors" 10 "errors"
9 "fmt" 11 "fmt"
10 "math" 12 "math"
11 "strings" 13 "strings"
12 14
13 ds "github.com/luci/gae/service/datastore" 15 ds "github.com/luci/gae/service/datastore"
14 "github.com/luci/gae/service/datastore/serialize" 16 "github.com/luci/gae/service/datastore/serialize"
17 "github.com/luci/luci-go/common/cmpbin"
15 ) 18 )
16 19
17 // MaxQueryComponents was lifted from a hard-coded constant in dev_appserver. 20 // 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 21 // No idea if it's a real limit or just a convenience in the current dev
19 // appserver implementation. 22 // appserver implementation.
20 const MaxQueryComponents = 100 23 const MaxQueryComponents = 100
21 24
22 var errQueryDone = errors.New("query is done") 25 var errQueryDone = errors.New("query is done")
23 26
24 type queryOp int 27 type queryOp int
(...skipping 29 matching lines...) Expand all
54 op = queryOpMap[toks[1]] 57 op = queryOpMap[toks[1]]
55 if op == qInvalid { 58 if op == qInvalid {
56 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f) 59 err = fmt.Errorf("datastore: invalid operator %q in filt er %q", toks[1], f)
57 } else { 60 } else {
58 prop = toks[0] 61 prop = toks[0]
59 } 62 }
60 } 63 }
61 return 64 return
62 } 65 }
63 66
64 type queryCursor string 67 // A queryCursor is:
68 // {#orders} ++ IndexColumn* ++ RawRowData
69 type queryCursor []byte
65 70
66 func (q queryCursor) String() string { return string(q) } 71 func (q queryCursor) String() string {
67 func (q queryCursor) Valid() bool { return q != "" } 72 » return base64.URLEncoding.EncodeToString([]byte(q))
73 }
74
75 func (q queryCursor) decode() (cols []ds.IndexColumn, data []byte, err error) {
76 » buf := bytes.NewBuffer([]byte(q))
77 » count, _, err := cmpbin.ReadUint(buf)
78 » if err != nil {
79 » » err = fmt.Errorf("invalid cursor")
80 » » return
81 » }
82
83 » if count > ds.MaxIndexColumns {
84 » » err = fmt.Errorf("invalid cursor")
85 » » return
86 » }
87
88 » cols = make([]ds.IndexColumn, count)
89 » for i := range cols {
90 » » if cols[i], err = serialize.ReadIndexColumn(buf); err != nil {
91 » » » err = fmt.Errorf("invalid cursor")
92 » » » return
93 » » }
94 » }
95
96 » data = buf.Bytes()
97 » return
98 }
68 99
69 type queryIneqFilter struct { 100 type queryIneqFilter struct {
70 prop string 101 prop string
71 102
72 » low *string 103 » low []byte
73 » high *string 104 » high []byte
74 } 105 }
75 106
76 func increment(bstr string, positive bool) string { 107 func increment(bstr []byte) []byte {
108 » // Copy bstr
109 » bstr = bjoin(bstr)
110
77 lastIdx := len(bstr) - 1 111 lastIdx := len(bstr) - 1
78 » last := bstr[lastIdx] 112 » if bstr[lastIdx] == 0xFF {
79 » if positive { 113 » » return append(bstr, 0)
80 » » if last == 0xFF {
81 » » » return bstr + "\x00"
82 » » }
83 » » return bstr[:lastIdx] + string([]byte{last + 1})
84 » } else {
85 » » if last == 0 {
86 » » » return bstr[:lastIdx-1]
87 » » }
88 » » return bstr[:lastIdx] + string([]byte{last - 1})
89 } 114 }
115 bstr[lastIdx] += 1
116 return bstr
90 } 117 }
91 118
92 // constrain 'folds' a new inequality into the current inequality filter. 119 // constrain 'folds' a new inequality into the current inequality filter.
93 // 120 //
94 // It will bump the high bound down, or the low bound up, assuming the incoming 121 // It will bump the high bound down, or the low bound up, assuming the incoming
95 // constraint does so. 122 // constraint does so.
96 // 123 //
97 // It returns true iff the filter is overconstrained (i.e. low > high) 124 // It returns true iff the filter is overconstrained (i.e. low > high)
98 func (q *queryIneqFilter) constrain(op queryOp, val string) bool { 125 func (q *queryIneqFilter) constrain(op queryOp, val []byte) bool {
99 switch op { 126 switch op {
127 case qLessEq:
128 val = increment(val)
129 fallthrough
100 case qLessThan: 130 case qLessThan:
101 val = increment(val, true)
102 fallthrough
103 case qLessEq:
104 // adjust upper bound downwards 131 // adjust upper bound downwards
105 » » if q.high == nil || *q.high > val { 132 » » if q.high == nil || bytes.Compare(q.high, val) > 0 {
106 » » » q.high = &val 133 » » » q.high = val
107 } 134 }
108 135
109 case qGreaterThan: 136 case qGreaterThan:
110 » » val = increment(val, false) 137 » » val = increment(val)
111 fallthrough 138 fallthrough
112 case qGreaterEq: 139 case qGreaterEq:
113 // adjust lower bound upwards 140 // adjust lower bound upwards
114 » » if q.low == nil || *q.low < val { 141 » » if q.low == nil || bytes.Compare(q.low, val) < 0 {
115 » » » q.low = &val 142 » » » q.low = val
116 } 143 }
117 144
118 default: 145 default:
119 panic(fmt.Errorf("constrain cannot handle filter op %d", op)) 146 panic(fmt.Errorf("constrain cannot handle filter op %d", op))
120 } 147 }
121 148
122 if q.low != nil && q.high != nil { 149 if q.low != nil && q.high != nil {
123 » » return *q.low > *q.high 150 » » return bytes.Compare(q.low, q.high) >= 0
124 } 151 }
125 return false 152 return false
126 } 153 }
127 154
128 type queryImpl struct { 155 type queryImpl struct {
129 ns string 156 ns string
130 157
131 kind string 158 kind string
132 ancestor ds.Key 159 ancestor ds.Key
133 160
134 // prop -> encoded values 161 // prop -> encoded values
135 eqFilters map[string]map[string]struct{} 162 eqFilters map[string]map[string]struct{}
136 ineqFilter queryIneqFilter 163 ineqFilter queryIneqFilter
137 order []ds.IndexColumn 164 order []ds.IndexColumn
138 » project map[string]struct{} 165 » project []string
139 166
140 distinct bool 167 distinct bool
141 eventualConsistency bool 168 eventualConsistency bool
142 keysOnly bool 169 keysOnly bool
143 limit int32 170 limit int32
144 offset int32 171 offset int32
145 172
146 start queryCursor 173 start queryCursor
147 end queryCursor 174 end queryCursor
148 175
149 err error 176 err error
150 } 177 }
151 178
152 var _ ds.Query = (*queryImpl)(nil) 179 var _ ds.Query = (*queryImpl)(nil)
153 180
154 func (q *queryImpl) valid(ns string, isTxn bool) (done bool, err error) { 181 func sortOrdersEqual(as, bs []ds.IndexColumn) bool {
155 » if q.err == errQueryDone { 182 » if len(as) != len(bs) {
156 » » done = true 183 » » return false
157 » } else if q.err != nil { 184 » }
158 » » err = q.err 185 » for i, a := range as {
159 » } else if ns != q.ns { 186 » » if a != bs[i] {
160 » » err = errors.New( 187 » » » return false
188 » » }
189 » }
190 » return true
191 }
192
193 func (q *queryImpl) prep(ns string, isTxn bool) (*reducedQuery, error) {
194 » if q.err != nil {
195 » » return nil, q.err
196 » }
197 » if ns != q.ns {
198 » » return nil, errors.New(
161 "gae/memory: Namespace mismatched. Query and Datastore d on't agree " + 199 "gae/memory: Namespace mismatched. Query and Datastore d on't agree " +
162 "on the current namespace") 200 "on the current namespace")
163 } else if isTxn && q.ancestor == nil { 201 } else if isTxn && q.ancestor == nil {
164 » » err = errors.New( 202 » » return nil, errors.New(
165 "gae/memory: Only ancestor queries are allowed inside tr ansactions") 203 "gae/memory: Only ancestor queries are allowed inside tr ansactions")
166 } else if q.numComponents() > MaxQueryComponents { 204 } else if q.numComponents() > MaxQueryComponents {
167 » » err = fmt.Errorf( 205 » » return nil, fmt.Errorf(
168 "gae/memory: query is too large. may not have more than "+ 206 "gae/memory: query is too large. may not have more than "+
169 "%d filters + sort orders + ancestor total: had %d", 207 "%d filters + sort orders + ancestor total: had %d",
170 MaxQueryComponents, q.numComponents()) 208 MaxQueryComponents, q.numComponents())
171 } else if len(q.project) == 0 && q.distinct { 209 } else if len(q.project) == 0 && q.distinct {
172 // This must be delayed, because q.Distinct().Project("foo") is a valid 210 // This must be delayed, because q.Distinct().Project("foo") is a valid
173 // construction. If we checked this in Distinct, it could be too early, and 211 // construction. If we checked this in Distinct, it could be too early, and
174 // checking it in Project doesn't matter. 212 // checking it in Project doesn't matter.
175 » » err = errors.New( 213 » » return nil, errors.New(
176 "gae/memory: Distinct() only makes sense on projection q ueries.") 214 "gae/memory: Distinct() only makes sense on projection q ueries.")
177 } 215 }
178 » return 216 » ret := &reducedQuery{
217 » » ns: q.ns,
218 » » kind: q.kind,
219 » » eqFilters: q.eqFilters,
220 » » suffixFormat: q.order,
221 » }
222 » if len(ret.suffixFormat) == 0 && q.ineqFilter.prop != "" {
223 » » ret.suffixFormat = []ds.IndexColumn{{Property: q.ineqFilter.prop }}
224 » }
225 » if q.ancestor != nil {
226 » » ret.ancestor = serialize.ToBytes(q.ancestor)
227 » }
228
229 » // The inequality is specified in natural (ascending) order in the query , but
230 » // the order information may indicate to use an descending index column. If
231 » // that's the case, then we must invert, swap and increment the inequali ty
232 » // endpoints.
233 » //
234 » // Invert so that the desired numbers are represented correctly in the i ndex.
235 » // Swap so that our iterators still go from >= low to < high.
236 » // Increment so that >= and < get correctly bounded (since the iterator is
237 » // still using natrual ordering)
238 » shouldInvertIneq := false
239 » if len(ret.suffixFormat) > 0 && ret.suffixFormat[0].Direction == ds.DESC ENDING {
240 » » shouldInvertIneq = true
241 » }
242
243 » err := error(nil)
244 » cols := []ds.IndexColumn(nil)
245 » if q.start != nil {
246 » » cols, ret.low, err = q.start.decode()
247 » » if err != nil {
248 » » » return nil, err
249 » » }
250 » » if !sortOrdersEqual(cols, ret.suffixFormat) {
251 » » » return nil, errors.New("gae/memory: start cursor is inva lid.")
252 » » }
253 » } else {
254 » » if shouldInvertIneq {
255 » » » ret.high = increment(invert(q.ineqFilter.low))
256 » » } else {
257 » » » ret.low = q.ineqFilter.low
258 » » }
259 » }
260
261 » if q.end != nil {
262 » » cols, ret.high, err = q.end.decode()
263 » » if err != nil {
264 » » » return nil, err
265 » » }
266 » » if !sortOrdersEqual(cols, ret.suffixFormat) {
267 » » » return nil, errors.New("gae/memory: end cursor is invali d.")
268 » » }
269 » } else {
270 » » if shouldInvertIneq {
271 » » » ret.low = increment(invert(q.ineqFilter.high))
272 » » } else {
273 » » » ret.high = q.ineqFilter.high
274 » » }
275 » }
276
277 » // Add any projection columns not mentioned in the user-defined order as
278 » // ASCENDING orders. Technically we could be smart and automatically use
279 » // a DESCENDING ordered index, if it fit, but the logic gets insane, sin ce all
280 » // suffixes of all used indexes need to be PRECISELY equal (and so you'd have
281 » // to hunt/invalidate/something to find the combination of indexes that are
282 » // compatible with each other as well as the query). If you want to use
283 » // a DESCENDING column, just add it to the user sort order, and this loo p will
284 » // not synthesize a new sort order for it.
285 » originalStop := len(ret.suffixFormat)
286 » for _, p := range q.project {
287 » » needAdd := true
288 » » // originalStop prevents this loop from getting longer every tim e we add
289 » » // a projected property.
290 » » for _, col := range ret.suffixFormat[:originalStop] {
291 » » » if col.Property == p {
292 » » » » needAdd = false
293 » » » » break
294 » » » }
295 » » }
296 » » if needAdd {
297 » » » ret.suffixFormat = append(ret.suffixFormat, ds.IndexColu mn{Property: p})
298 » » }
299 » }
300
301 » return ret, nil
302
179 } 303 }
180 304
181 func (q *queryImpl) numComponents() int { 305 func (q *queryImpl) numComponents() int {
182 numComponents := len(q.order) 306 numComponents := len(q.order)
183 if q.ineqFilter.prop != "" { 307 if q.ineqFilter.prop != "" {
184 if q.ineqFilter.low != nil { 308 if q.ineqFilter.low != nil {
185 numComponents++ 309 numComponents++
186 } 310 }
187 if q.ineqFilter.high != nil { 311 if q.ineqFilter.high != nil {
188 numComponents++ 312 numComponents++
189 } 313 }
190 } 314 }
191 for _, v := range q.eqFilters { 315 for _, v := range q.eqFilters {
192 numComponents += len(v) 316 numComponents += len(v)
193 } 317 }
194 if q.ancestor != nil { 318 if q.ancestor != nil {
195 numComponents++ 319 numComponents++
196 } 320 }
197 return numComponents 321 return numComponents
198 } 322 }
199 323
200 func (q *queryImpl) calculateIndex() *ds.IndexDefinition {
201 // as a nod to simplicity in this code, we'll require that a single inde x
202 // is able to service the entire query. E.g. no zigzag merge joins or
203 // multiqueries. This will mean that the user will need to rely on
204 // dev_appserver to tell them what indicies they need for real, and for thier
205 // tests they'll need to specify the missing composite indices manually.
206 //
207 // This COULD lead to an exploding indicies problem, but we can fix that when
208 // we get to it.
209
210 //sortOrders := []qSortBy{}
211
212 return nil
213 }
214
215 // checkMutateClone sees if the query has an error. If not, it clones the query, 324 // checkMutateClone sees if the query has an error. If not, it clones the query,
216 // and assigns the output of `check` to the query error slot. If check returns 325 // and assigns the output of `check` to the query error slot. If check returns
217 // nil, it calls `mutate` on the cloned query. The (possibly new) query is then 326 // nil, it calls `mutate` on the cloned query. The (possibly new) query is then
218 // returned. 327 // returned.
219 func (q *queryImpl) checkMutateClone(check func() error, mutate func(*queryImpl) ) *queryImpl { 328 func (q *queryImpl) checkMutateClone(check func() error, mutate func(*queryImpl) ) *queryImpl {
220 if q.err != nil { 329 if q.err != nil {
221 return q 330 return q
222 } 331 }
223 nq := *q 332 nq := *q
224 nq.eqFilters = make(map[string]map[string]struct{}, len(q.eqFilters)) 333 nq.eqFilters = make(map[string]map[string]struct{}, len(q.eqFilters))
225 for prop, vals := range q.eqFilters { 334 for prop, vals := range q.eqFilters {
226 nq.eqFilters[prop] = make(map[string]struct{}, len(vals)) 335 nq.eqFilters[prop] = make(map[string]struct{}, len(vals))
227 for v := range vals { 336 for v := range vals {
228 nq.eqFilters[prop][v] = struct{}{} 337 nq.eqFilters[prop][v] = struct{}{}
229 } 338 }
230 } 339 }
231 nq.order = make([]ds.IndexColumn, len(q.order)) 340 nq.order = make([]ds.IndexColumn, len(q.order))
232 copy(nq.order, q.order) 341 copy(nq.order, q.order)
233 » nq.project = make(map[string]struct{}, len(q.project)) 342 » nq.project = make([]string, len(q.project))
234 » for f := range q.project { 343 » copy(nq.project, q.project)
235 » » nq.project[f] = struct{}{}
236 » }
237 if check != nil { 344 if check != nil {
238 nq.err = check() 345 nq.err = check()
239 } 346 }
240 if nq.err == nil { 347 if nq.err == nil {
241 mutate(&nq) 348 mutate(&nq)
242 } 349 }
243 return &nq 350 return &nq
244 } 351 }
245 352
246 func (q *queryImpl) Ancestor(k ds.Key) ds.Query { 353 func (q *queryImpl) Ancestor(k ds.Key) ds.Query {
247 return q.checkMutateClone( 354 return q.checkMutateClone(
248 func() error { 355 func() error {
249 if k == nil { 356 if k == nil {
250 // SDK has an explicit nil-check 357 // SDK has an explicit nil-check
251 return errors.New("datastore: nil query ancestor ") 358 return errors.New("datastore: nil query ancestor ")
252 } 359 }
360 if k.Namespace() != q.ns {
361 return fmt.Errorf("bad namespace: %q (expected % q)", k.Namespace(), q.ns)
362 }
253 if !k.Valid(false, globalAppID, q.ns) { 363 if !k.Valid(false, globalAppID, q.ns) {
254 // technically the SDK implementation does a Wei rd Thing (tm) if both the 364 // technically the SDK implementation does a Wei rd Thing (tm) if both the
255 // stringID and intID are set on a key; it only serializes the stringID in 365 // stringID and intID are set on a key; it only serializes the stringID in
256 // the proto. This means that if you set the Anc estor to an invalid key, 366 // the proto. This means that if you set the Anc estor to an invalid key,
257 // you'll never actually hear about it. Instead of doing that insanity, we 367 // you'll never actually hear about it. Instead of doing that insanity, we
258 // just swap to an error here. 368 // just swap to an error here.
259 return ds.ErrInvalidKey 369 return ds.ErrInvalidKey
260 } 370 }
261 if k.Namespace() != q.ns {
262 return fmt.Errorf("bad namespace: %q (expected % q)", k.Namespace(), q.ns)
263 }
264 if q.ancestor != nil { 371 if q.ancestor != nil {
265 return errors.New("cannot have more than one anc estor") 372 return errors.New("cannot have more than one anc estor")
266 } 373 }
267 return nil 374 return nil
268 }, 375 },
269 func(q *queryImpl) { 376 func(q *queryImpl) {
270 q.ancestor = k 377 q.ancestor = k
271 }) 378 })
272 } 379 }
273 380
274 func (q *queryImpl) Distinct() ds.Query { 381 func (q *queryImpl) Distinct() ds.Query {
275 return q.checkMutateClone(nil, func(q *queryImpl) { 382 return q.checkMutateClone(nil, func(q *queryImpl) {
276 q.distinct = true 383 q.distinct = true
277 }) 384 })
278 } 385 }
279 386
280 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query { 387 func (q *queryImpl) Filter(fStr string, val interface{}) ds.Query {
281 prop := "" 388 prop := ""
282 op := qInvalid 389 op := qInvalid
283 » binVal := "" 390 » binVal := []byte(nil)
284 return q.checkMutateClone( 391 return q.checkMutateClone(
285 func() error { 392 func() error {
286 var err error 393 var err error
287 prop, op, err = parseFilter(fStr) 394 prop, op, err = parseFilter(fStr)
288 if err != nil { 395 if err != nil {
289 return err 396 return err
290 } 397 }
291 398
292 if q.kind == "" && prop != "__key__" { 399 if q.kind == "" && prop != "__key__" {
293 // https://cloud.google.com/appengine/docs/go/da tastore/queries#Go_Kindless_queries 400 // https://cloud.google.com/appengine/docs/go/da tastore/queries#Go_Kindless_queries
(...skipping 27 matching lines...) Expand all
321 if q.ineqFilter.prop != "" && q.ineqFilter.prop != prop { 428 if q.ineqFilter.prop != "" && q.ineqFilter.prop != prop {
322 return fmt.Errorf( 429 return fmt.Errorf(
323 "inequality filters on multiple properties: %q and %q", 430 "inequality filters on multiple properties: %q and %q",
324 q.ineqFilter.prop, prop) 431 q.ineqFilter.prop, prop)
325 } 432 }
326 if len(q.order) > 0 && q.order[0].Property != pr op { 433 if len(q.order) > 0 && q.order[0].Property != pr op {
327 return fmt.Errorf( 434 return fmt.Errorf(
328 "first sort order must match ine quality filter: %q v %q", 435 "first sort order must match ine quality filter: %q v %q",
329 q.order[0].Property, prop) 436 q.order[0].Property, prop)
330 } 437 }
331 » » » } else if _, ok := q.project[prop]; ok { 438 » » » } else {
332 » » » » return fmt.Errorf( 439 » » » » for _, p := range q.project {
333 » » » » » "cannot project on field which is used i n an equality filter: %q", 440 » » » » » if p == prop {
334 » » » » » prop) 441 » » » » » » return fmt.Errorf(
442 » » » » » » » "cannot project on field which is used in an equality filter: %q",
443 » » » » » » » prop)
444 » » » » » }
445 » » » » }
335 } 446 }
336 » » » binVal = string(serialize.ToBytes(p)) 447 » » » binVal = serialize.ToBytes(p)
337 return err 448 return err
338 }, 449 },
339 func(q *queryImpl) { 450 func(q *queryImpl) {
340 if op == qEqual { 451 if op == qEqual {
341 // add it to eq filters 452 // add it to eq filters
342 if _, ok := q.eqFilters[prop]; !ok { 453 if _, ok := q.eqFilters[prop]; !ok {
343 » » » » » q.eqFilters[prop] = map[string]struct{}{ binVal: {}} 454 » » » » » q.eqFilters[prop] = map[string]struct{}{ string(binVal): {}}
344 } else { 455 } else {
345 » » » » » q.eqFilters[prop][binVal] = struct{}{} 456 » » » » » q.eqFilters[prop][string(binVal)] = stru ct{}{}
346 } 457 }
347 458
348 // remove it from sort orders. 459 // remove it from sort orders.
349 // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters 460 // https://cloud.google.com/appengine/docs/go/da tastore/queries#sort_orders_are_ignored_on_properties_with_equality_filters
350 toRm := -1 461 toRm := -1
351 for i, o := range q.order { 462 for i, o := range q.order {
352 if o.Property == prop { 463 if o.Property == prop {
353 toRm = i 464 toRm = i
354 break 465 break
355 } 466 }
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
412 } 523 }
413 }) 524 })
414 } 525 }
415 526
416 func (q *queryImpl) Project(fieldName ...string) ds.Query { 527 func (q *queryImpl) Project(fieldName ...string) ds.Query {
417 return q.checkMutateClone( 528 return q.checkMutateClone(
418 func() error { 529 func() error {
419 if q.keysOnly { 530 if q.keysOnly {
420 return errors.New("cannot project a keysOnly que ry") 531 return errors.New("cannot project a keysOnly que ry")
421 } 532 }
533 dupCheck := map[string]struct{}{}
422 for _, f := range fieldName { 534 for _, f := range fieldName {
535 if _, ok := dupCheck[f]; ok {
536 return fmt.Errorf("cannot project on the same field twice: %q", f)
537 }
538 dupCheck[f] = struct{}{}
423 if f == "" { 539 if f == "" {
424 return errors.New("cannot project on an empty field name") 540 return errors.New("cannot project on an empty field name")
425 } 541 }
426 » » » » if strings.HasPrefix(f, "__") && strings.HasSuff ix(f, "__") { 542 » » » » if f == "__key__" {
427 » » » » » return fmt.Errorf("cannot project on %q" , f) 543 » » » » » return fmt.Errorf("cannot project on __k ey__")
428 } 544 }
429 if _, ok := q.eqFilters[f]; ok { 545 if _, ok := q.eqFilters[f]; ok {
430 return fmt.Errorf( 546 return fmt.Errorf(
431 "cannot project on field which i s used in an equality filter: %q", f) 547 "cannot project on field which i s used in an equality filter: %q", f)
432 } 548 }
549 for _, p := range q.project {
550 if p == f {
551 return fmt.Errorf("cannot projec t on the same field twice: %q", f)
552 }
553 }
433 } 554 }
434 return nil 555 return nil
435 }, 556 },
436 func(q *queryImpl) { 557 func(q *queryImpl) {
437 » » » for _, f := range fieldName { 558 » » » q.project = append(q.project, fieldName...)
438 » » » » q.project[f] = struct{}{}
439 » » » }
440 }) 559 })
441 } 560 }
442 561
443 func (q *queryImpl) KeysOnly() ds.Query { 562 func (q *queryImpl) KeysOnly() ds.Query {
444 return q.checkMutateClone( 563 return q.checkMutateClone(
445 func() error { 564 func() error {
446 if len(q.project) != 0 { 565 if len(q.project) != 0 {
447 return errors.New("cannot project a keysOnly que ry") 566 return errors.New("cannot project a keysOnly que ry")
448 } 567 }
449 return nil 568 return nil
(...skipping 25 matching lines...) Expand all
475 if offset > math.MaxInt32 { 594 if offset > math.MaxInt32 {
476 return errors.New("datastore: query offset overf low") 595 return errors.New("datastore: query offset overf low")
477 } 596 }
478 return nil 597 return nil
479 }, 598 },
480 func(q *queryImpl) { 599 func(q *queryImpl) {
481 q.offset = int32(offset) 600 q.offset = int32(offset)
482 }) 601 })
483 } 602 }
484 603
604 func queryCursorCheck(ns, flavor string, current queryCursor, newCursor ds.Curso r) (queryCursor, error) {
605 if current != nil {
606 return nil, fmt.Errorf("%s cursor is multiply defined", flavor)
607 }
608 curs, ok := newCursor.(queryCursor)
609 if !ok {
610 return nil, fmt.Errorf("%s cursor is unknown type: %T", flavor, curs)
611 }
612 _, _, err := curs.decode()
613 return curs, err
614 }
615
485 func (q *queryImpl) Start(c ds.Cursor) ds.Query { 616 func (q *queryImpl) Start(c ds.Cursor) ds.Query {
486 » curs := queryCursor("") 617 » curs := queryCursor(nil)
487 return q.checkMutateClone( 618 return q.checkMutateClone(
488 » » func() error { 619 » » func() (err error) {
489 » » » ok := false 620 » » » curs, err = queryCursorCheck(q.ns, "start", q.start, c)
490 » » » if curs, ok = c.(queryCursor); !ok { 621 » » » return
491 » » » » return fmt.Errorf("start cursor is unknown type: %T", c)
492 » » » }
493 » » » if !curs.Valid() {
494 » » » » return errors.New("datastore: invalid cursor")
495 » » » }
496 » » » return nil
497 }, 622 },
498 func(q *queryImpl) { 623 func(q *queryImpl) {
499 q.start = curs 624 q.start = curs
500 }) 625 })
501 } 626 }
502 627
503 func (q *queryImpl) End(c ds.Cursor) ds.Query { 628 func (q *queryImpl) End(c ds.Cursor) ds.Query {
504 » curs := queryCursor("") 629 » curs := queryCursor(nil)
505 return q.checkMutateClone( 630 return q.checkMutateClone(
506 » » func() error { 631 » » func() (err error) {
507 » » » ok := false 632 » » » curs, err = queryCursorCheck(q.ns, "end", q.end, c)
508 » » » if curs, ok = c.(queryCursor); !ok { 633 » » » return
509 » » » » return fmt.Errorf("end cursor is unknown type: % T", c)
510 » » » }
511 » » » if !curs.Valid() {
512 » » » » return errors.New("datastore: invalid cursor")
513 » » » }
514 » » » return nil
515 }, 634 },
516 func(q *queryImpl) { 635 func(q *queryImpl) {
517 q.end = curs 636 q.end = curs
518 }) 637 })
519 } 638 }
520 639
521 func (q *queryImpl) EventualConsistency() ds.Query { 640 func (q *queryImpl) EventualConsistency() ds.Query {
522 return q.checkMutateClone( 641 return q.checkMutateClone(
523 nil, func(q *queryImpl) { 642 nil, func(q *queryImpl) {
524 q.eventualConsistency = true 643 q.eventualConsistency = true
525 }) 644 })
526 } 645 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698