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

Side by Side Diff: go/src/infra/gae/libs/wrapper/memory/datastore_query.go

Issue 1230303003: Revert "Refactor current GAE abstraction library to be free of the SDK*" (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Created 5 years, 5 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 "bytes"
9 "errors" 9 "errors"
10 "fmt" 10 "fmt"
11 "math" 11 "math"
12 "strings" 12 "strings"
13 13
14 » "infra/gae/libs/gae" 14 » "appengine/datastore"
15 » "infra/gae/libs/gae/helper" 15 » pb "appengine_internal/datastore"
16 16
17 "github.com/luci/gkvlite" 17 "github.com/luci/gkvlite"
18 "github.com/luci/luci-go/common/cmpbin" 18 "github.com/luci/luci-go/common/cmpbin"
19
20 "infra/gae/libs/wrapper"
19 ) 21 )
20 22
21 type qDirection bool 23 type qDirection bool
22 24
23 const ( 25 const (
24 qASC qDirection = true 26 qASC qDirection = true
25 qDEC = false 27 qDEC = false
26 ) 28 )
27 29
28 var builtinQueryPrefix = []byte{0} 30 var builtinQueryPrefix = []byte{0}
29 var complexQueryPrefix = []byte{1} 31 var complexQueryPrefix = []byte{1}
30 32
31 type qSortBy struct { 33 type qSortBy struct {
32 prop string 34 prop string
33 dir qDirection 35 dir qDirection
34 } 36 }
35 37
36 func (q qSortBy) WriteBinary(buf *bytes.Buffer) { 38 func (q qSortBy) WriteBinary(buf *bytes.Buffer) {
37 if q.dir == qASC { 39 if q.dir == qASC {
38 buf.WriteByte(0) 40 buf.WriteByte(0)
39 } else { 41 } else {
40 buf.WriteByte(1) 42 buf.WriteByte(1)
41 } 43 }
42 » cmpbin.WriteString(buf, q.prop) 44 » writeString(buf, q.prop)
43 } 45 }
44 46
45 func (q *qSortBy) ReadBinary(buf *bytes.Buffer) error { 47 func (q *qSortBy) ReadBinary(buf *bytes.Buffer) error {
46 dir, err := buf.ReadByte() 48 dir, err := buf.ReadByte()
47 if err != nil { 49 if err != nil {
48 return err 50 return err
49 } 51 }
50 q.dir = dir == 0 52 q.dir = dir == 0
51 » q.prop, _, err = cmpbin.ReadString(buf) 53 » q.prop, err = readString(buf)
52 return err 54 return err
53 } 55 }
54 56
55 type qIndex struct { 57 type qIndex struct {
56 kind string 58 kind string
57 ancestor bool 59 ancestor bool
58 sortby []qSortBy 60 sortby []qSortBy
59 } 61 }
60 62
61 func (i *qIndex) Builtin() bool { 63 func (i *qIndex) Builtin() bool {
62 return !i.ancestor && len(i.sortby) <= 1 64 return !i.ancestor && len(i.sortby) <= 1
63 } 65 }
64 66
65 func (i *qIndex) Less(o *qIndex) bool {
66 ibuf, obuf := &bytes.Buffer{}, &bytes.Buffer{}
67 i.WriteBinary(ibuf)
68 o.WriteBinary(obuf)
69 return i.String() < o.String()
70 }
71
72 // Valid verifies that this qIndex doesn't have duplicate sortBy fields. 67 // Valid verifies that this qIndex doesn't have duplicate sortBy fields.
73 func (i *qIndex) Valid() bool { 68 func (i *qIndex) Valid() bool {
74 names := map[string]bool{} 69 names := map[string]bool{}
75 for _, sb := range i.sortby { 70 for _, sb := range i.sortby {
76 if names[sb.prop] { 71 if names[sb.prop] {
77 return false 72 return false
78 } 73 }
79 names[sb.prop] = true 74 names[sb.prop] = true
80 } 75 }
81 return true 76 return true
82 } 77 }
83 78
84 func (i *qIndex) WriteBinary(buf *bytes.Buffer) { 79 func (i *qIndex) WriteBinary(buf *bytes.Buffer) {
85 // TODO(riannucci): do a Grow call here? 80 // TODO(riannucci): do a Grow call here?
86 if i.Builtin() { 81 if i.Builtin() {
87 buf.Write(builtinQueryPrefix) 82 buf.Write(builtinQueryPrefix)
88 } else { 83 } else {
89 buf.Write(complexQueryPrefix) 84 buf.Write(complexQueryPrefix)
90 } 85 }
91 » cmpbin.WriteString(buf, i.kind) 86 » writeString(buf, i.kind)
92 if i.ancestor { 87 if i.ancestor {
93 buf.WriteByte(0) 88 buf.WriteByte(0)
94 } else { 89 } else {
95 buf.WriteByte(1) 90 buf.WriteByte(1)
96 } 91 }
97 cmpbin.WriteUint(buf, uint64(len(i.sortby))) 92 cmpbin.WriteUint(buf, uint64(len(i.sortby)))
98 for _, sb := range i.sortby { 93 for _, sb := range i.sortby {
99 sb.WriteBinary(buf) 94 sb.WriteBinary(buf)
100 } 95 }
101 } 96 }
(...skipping 20 matching lines...) Expand all
122 return ret.String() 117 return ret.String()
123 } 118 }
124 119
125 func (i *qIndex) ReadBinary(buf *bytes.Buffer) error { 120 func (i *qIndex) ReadBinary(buf *bytes.Buffer) error {
126 // discard builtin/complex byte 121 // discard builtin/complex byte
127 _, err := buf.ReadByte() 122 _, err := buf.ReadByte()
128 if err != nil { 123 if err != nil {
129 return err 124 return err
130 } 125 }
131 126
132 » i.kind, _, err = cmpbin.ReadString(buf) 127 » i.kind, err = readString(buf)
133 if err != nil { 128 if err != nil {
134 return err 129 return err
135 } 130 }
136 anc, err := buf.ReadByte() 131 anc, err := buf.ReadByte()
137 if err != nil { 132 if err != nil {
138 return err 133 return err
139 } 134 }
140 i.ancestor = anc == 1 135 i.ancestor = anc == 1
141 136
142 numSorts, _, err := cmpbin.ReadUint(buf) 137 numSorts, _, err := cmpbin.ReadUint(buf)
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
211 field string 206 field string
212 direction qDirection 207 direction qDirection
213 } 208 }
214 209
215 type queryCursor string 210 type queryCursor string
216 211
217 func (q queryCursor) String() string { return string(q) } 212 func (q queryCursor) String() string { return string(q) }
218 func (q queryCursor) Valid() bool { return q != "" } 213 func (q queryCursor) Valid() bool { return q != "" }
219 214
220 type queryImpl struct { 215 type queryImpl struct {
221 » gae.DSQuery 216 » wrapper.DSQuery
222 217
223 ns string 218 ns string
224 219
225 kind string 220 kind string
226 » ancestor gae.DSKey 221 » ancestor *datastore.Key
227 filter []queryFilter 222 filter []queryFilter
228 order []queryOrder 223 order []queryOrder
229 224
230 keysOnly bool 225 keysOnly bool
231 limit int32 226 limit int32
232 offset int32 227 offset int32
233 228
234 start queryCursor 229 start queryCursor
235 end queryCursor 230 end queryCursor
236 231
237 err error 232 err error
238 } 233 }
239 234
240 type queryIterImpl struct { 235 type queryIterImpl struct {
241 idx *queryImpl 236 idx *queryImpl
242 } 237 }
243 238
244 func (q *queryIterImpl) Cursor() (gae.DSCursor, error) { 239 func (q *queryIterImpl) Cursor() (wrapper.DSCursor, error) {
245 if q.idx.err != nil { 240 if q.idx.err != nil {
246 return nil, q.idx.err 241 return nil, q.idx.err
247 } 242 }
248 return nil, nil 243 return nil, nil
249 } 244 }
250 245
251 func (q *queryIterImpl) Next(dst interface{}) (gae.DSKey, error) { 246 func (q *queryIterImpl) Next(dst interface{}) (*datastore.Key, error) {
252 if q.idx.err != nil { 247 if q.idx.err != nil {
253 return nil, q.idx.err 248 return nil, q.idx.err
254 } 249 }
255 return nil, nil 250 return nil, nil
256 } 251 }
257 252
258 func (q *queryImpl) normalize() (ret *queryImpl) { 253 func (q *queryImpl) normalize() (ret *queryImpl) {
259 // ported from GAE SDK datastore_index.py;Normalize() 254 // ported from GAE SDK datastore_index.py;Normalize()
260 ret = q.clone() 255 ret = q.clone()
261 256
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
333 ret.order = newOrders 328 ret.order = newOrders
334 329
335 return 330 return
336 } 331 }
337 332
338 func (q *queryImpl) checkCorrectness(ns string, isTxn bool) (ret *queryImpl) { 333 func (q *queryImpl) checkCorrectness(ns string, isTxn bool) (ret *queryImpl) {
339 // ported from GAE SDK datastore_stub_util.py;CheckQuery() 334 // ported from GAE SDK datastore_stub_util.py;CheckQuery()
340 ret = q.clone() 335 ret = q.clone()
341 336
342 if ns != ret.ns { 337 if ns != ret.ns {
343 » » ret.err = errors.New( 338 » » ret.err = newDSError(pb.Error_BAD_REQUEST,
344 » » » "gae/memory: Namespace mismatched. Query and Datastore d on't agree " + 339 » » » "MADE UP ERROR: Namespace mismatched. Query and Datastor e don't agree "+
345 "on the current namespace") 340 "on the current namespace")
346 return 341 return
347 } 342 }
348 343
349 if ret.err != nil { 344 if ret.err != nil {
350 return 345 return
351 } 346 }
352 347
353 // if projection && keys_only: 348 // if projection && keys_only:
354 // "projection and keys_only cannot both be set" 349 // "projection and keys_only cannot both be set"
355 350
356 // if projection props match /^__.*__$/: 351 // if projection props match /^__.*__$/:
357 // "projections are not supported for the property: %(prop)s" 352 // "projections are not supported for the property: %(prop)s"
358 353
359 if isTxn && ret.ancestor == nil { 354 if isTxn && ret.ancestor == nil {
360 » » ret.err = errors.New( 355 » » ret.err = newDSError(pb.Error_BAD_REQUEST,
361 » » » "gae/memory: Only ancestor queries are allowed inside tr ansactions") 356 » » » "Only ancestor queries are allowed inside transactions")
362 return 357 return
363 } 358 }
364 359
365 numComponents := len(ret.filter) + len(ret.order) 360 numComponents := len(ret.filter) + len(ret.order)
366 if ret.ancestor != nil { 361 if ret.ancestor != nil {
367 numComponents++ 362 numComponents++
368 } 363 }
369 if numComponents > 100 { 364 if numComponents > 100 {
370 » » ret.err = errors.New( 365 » » ret.err = newDSError(pb.Error_BAD_REQUEST,
371 » » » "gae/memory: query is too large. may not have more than " + 366 » » » "query is too large. may not have more than "+
372 "100 filters + sort orders ancestor total") 367 "100 filters + sort orders ancestor total")
373 } 368 }
374 369
375 // if ret.ancestor.appid() != current appid 370 // if ret.ancestor.appid() != current appid
376 // "query app is x but ancestor app is x" 371 // "query app is x but ancestor app is x"
377 // if ret.ancestor.namespace() != current namespace 372 // if ret.ancestor.namespace() != current namespace
378 // "query namespace is x but ancestor namespace is x" 373 // "query namespace is x but ancestor namespace is x"
379 374
380 // if not all(g in orders for g in group_by) 375 // if not all(g in orders for g in group_by)
381 // "items in the group by clause must be specified first in the orderin g" 376 // "items in the group by clause must be specified first in the orderin g"
382 377
383 ineqPropName := "" 378 ineqPropName := ""
384 for _, f := range ret.filter { 379 for _, f := range ret.filter {
385 if f.field == "__key__" { 380 if f.field == "__key__" {
386 » » » k, ok := f.value.(gae.DSKey) 381 » » » k, ok := f.value.(*datastore.Key)
387 if !ok { 382 if !ok {
388 » » » » ret.err = errors.New( 383 » » » » ret.err = newDSError(pb.Error_BAD_REQUEST,
389 » » » » » "gae/memory: __key__ filter value must b e a Key") 384 » » » » » "__key__ filter value must be a Key")
390 return 385 return
391 } 386 }
392 » » » if !helper.DSKeyValid(k, ret.ns, false) { 387 » » » if !keyValid(ret.ns, k, userKeyOnly) {
393 // See the comment in queryImpl.Ancestor; basica lly this check 388 // See the comment in queryImpl.Ancestor; basica lly this check
394 // never happens in the real env because the SDK silently swallows 389 // never happens in the real env because the SDK silently swallows
395 // this condition :/ 390 // this condition :/
396 » » » » ret.err = gae.ErrDSInvalidKey 391 » » » » ret.err = datastore.ErrInvalidKey
397 return 392 return
398 } 393 }
399 // __key__ filter app is X but query app is X 394 // __key__ filter app is X but query app is X
400 // __key__ filter namespace is X but query namespace is X 395 // __key__ filter namespace is X but query namespace is X
401 } 396 }
402 // if f.op == qEqual and f.field in ret.project_fields 397 // if f.op == qEqual and f.field in ret.project_fields
403 // "cannot use projection on a proprety with an equality filte r" 398 // "cannot use projection on a proprety with an equality filte r"
404 399
405 if f.op.isINEQOp() { 400 if f.op.isINEQOp() {
406 if ineqPropName == "" { 401 if ineqPropName == "" {
407 ineqPropName = f.field 402 ineqPropName = f.field
408 } else if f.field != ineqPropName { 403 } else if f.field != ineqPropName {
409 » » » » ret.err = fmt.Errorf( 404 » » » » ret.err = newDSError(pb.Error_BAD_REQUEST,
410 » » » » » "gae/memory: Only one inequality filter per query is supported. "+ 405 » » » » » fmt.Sprintf(
411 » » » » » » "Encountered both %s and %s", in eqPropName, f.field) 406 » » » » » » "Only one inequality filter per query is supported. "+
407 » » » » » » » "Encountered both %s and %s", ineqPropName, f.field))
412 return 408 return
413 } 409 }
414 } 410 }
415 } 411 }
416 412
417 // if ineqPropName != "" && len(group_by) > 0 && len(orders) ==0 413 // if ineqPropName != "" && len(group_by) > 0 && len(orders) ==0
418 // "Inequality filter on X must also be a group by property "+ 414 // "Inequality filter on X must also be a group by property "+
419 // "when group by properties are set." 415 // "when group by properties are set."
420 416
421 if ineqPropName != "" && len(ret.order) != 0 { 417 if ineqPropName != "" && len(ret.order) != 0 {
422 if ret.order[0].field != ineqPropName { 418 if ret.order[0].field != ineqPropName {
423 » » » ret.err = fmt.Errorf( 419 » » » ret.err = newDSError(pb.Error_BAD_REQUEST,
424 » » » » "gae/memory: The first sort property must be the same as the property "+ 420 » » » » fmt.Sprintf(
425 » » » » » "to which the inequality filter is appli ed. In your query "+ 421 » » » » » "The first sort property must be the sam e as the property "+
426 » » » » » "the first sort property is %s but the i nequality filter "+ 422 » » » » » » "to which the inequality filter is applied. In your query "+
427 » » » » » "is on %s", ret.order[0].field, ineqProp Name) 423 » » » » » » "the first sort property is %s b ut the inequality filter "+
424 » » » » » » "is on %s", ret.order[0].field, ineqPropName))
428 return 425 return
429 } 426 }
430 } 427 }
431 428
432 if ret.kind == "" { 429 if ret.kind == "" {
433 for _, f := range ret.filter { 430 for _, f := range ret.filter {
434 if f.field != "__key__" { 431 if f.field != "__key__" {
435 » » » » ret.err = errors.New( 432 » » » » ret.err = newDSError(pb.Error_BAD_REQUEST,
436 » » » » » "gae/memory: kind is required for non-__ key__ filters") 433 » » » » » "kind is required for non-__key__ filter s")
437 return 434 return
438 } 435 }
439 } 436 }
440 for _, o := range ret.order { 437 for _, o := range ret.order {
441 if o.field != "__key__" || o.direction != qASC { 438 if o.field != "__key__" || o.direction != qASC {
442 » » » » ret.err = errors.New( 439 » » » » ret.err = newDSError(pb.Error_BAD_REQUEST,
443 » » » » » "gae/memory: kind is required for all or ders except __key__ ascending") 440 » » » » » "kind is required for all orders except __key__ ascending")
444 return 441 return
445 } 442 }
446 } 443 }
447 } 444 }
448 return 445 return
449 } 446 }
450 447
451 func (q *queryImpl) calculateIndex() *qIndex { 448 func (q *queryImpl) calculateIndex() *qIndex {
452 // as a nod to simplicity in this code, we'll require that a single inde x 449 // as a nod to simplicity in this code, we'll require that a single inde x
453 // is able to service the entire query. E.g. no zigzag merge joins or 450 // is able to service the entire query. E.g. no zigzag merge joins or
454 // multiqueries. This will mean that the user will need to rely on 451 // multiqueries. This will mean that the user will need to rely on
455 // dev_appserver to tell them what indicies they need for real, and for thier 452 // dev_appserver to tell them what indicies they need for real, and for thier
456 // tests they'll need to specify the missing composite indices manually. 453 // tests they'll need to specify the missing composite indices manually.
457 // 454 //
458 // This COULD lead to an exploding indicies problem, but we can fix that when 455 // This COULD lead to an exploding indicies problem, but we can fix that when
459 // we get to it. 456 // we get to it.
460 457
461 //sortOrders := []qSortBy{} 458 //sortOrders := []qSortBy{}
462 459
463 return nil 460 return nil
464 } 461 }
465 462
466 func (q *queryImpl) clone() *queryImpl { 463 func (q *queryImpl) clone() *queryImpl {
467 ret := *q 464 ret := *q
468 ret.filter = append([]queryFilter(nil), q.filter...) 465 ret.filter = append([]queryFilter(nil), q.filter...)
469 ret.order = append([]queryOrder(nil), q.order...) 466 ret.order = append([]queryOrder(nil), q.order...)
470 return &ret 467 return &ret
471 } 468 }
472 469
473 func (q *queryImpl) Ancestor(k gae.DSKey) gae.DSQuery { 470 func (q *queryImpl) Ancestor(k *datastore.Key) wrapper.DSQuery {
474 q = q.clone() 471 q = q.clone()
475 q.ancestor = k 472 q.ancestor = k
476 if k == nil { 473 if k == nil {
477 // SDK has an explicit nil-check 474 // SDK has an explicit nil-check
478 q.err = errors.New("datastore: nil query ancestor") 475 q.err = errors.New("datastore: nil query ancestor")
479 » } else if !helper.DSKeyValid(k, q.ns, false) { 476 » } else if !keyValid(q.ns, k, userKeyOnly) {
480 // technically the SDK implementation does a Weird Thing (tm) if both the 477 // technically the SDK implementation does a Weird Thing (tm) if both the
481 // stringID and intID are set on a key; it only serializes the s tringID in 478 // stringID and intID are set on a key; it only serializes the s tringID in
482 // the proto. This means that if you set the Ancestor to an inva lid key, 479 // the proto. This means that if you set the Ancestor to an inva lid key,
483 // you'll never actually hear about it. Instead of doing that in sanity, we 480 // you'll never actually hear about it. Instead of doing that in sanity, we
484 // just swap to an error here. 481 // just swap to an error here.
485 » » q.err = gae.ErrDSInvalidKey 482 » » q.err = datastore.ErrInvalidKey
486 } 483 }
487 return q 484 return q
488 } 485 }
489 486
490 func (q *queryImpl) Filter(fStr string, val interface{}) gae.DSQuery { 487 func (q *queryImpl) Filter(fStr string, val interface{}) wrapper.DSQuery {
491 q = q.clone() 488 q = q.clone()
492 f, err := parseFilter(fStr, val) 489 f, err := parseFilter(fStr, val)
493 if err != nil { 490 if err != nil {
494 q.err = err 491 q.err = err
495 return q 492 return q
496 } 493 }
497 q.filter = append(q.filter, f) 494 q.filter = append(q.filter, f)
498 return q 495 return q
499 } 496 }
500 497
501 func (q *queryImpl) Order(field string) gae.DSQuery { 498 func (q *queryImpl) Order(field string) wrapper.DSQuery {
502 q = q.clone() 499 q = q.clone()
503 field = strings.TrimSpace(field) 500 field = strings.TrimSpace(field)
504 o := queryOrder{field, qASC} 501 o := queryOrder{field, qASC}
505 if strings.HasPrefix(field, "-") { 502 if strings.HasPrefix(field, "-") {
506 o.direction = qDEC 503 o.direction = qDEC
507 o.field = strings.TrimSpace(field[1:]) 504 o.field = strings.TrimSpace(field[1:])
508 } else if strings.HasPrefix(field, "+") { 505 } else if strings.HasPrefix(field, "+") {
509 q.err = fmt.Errorf("datastore: invalid order: %q", field) 506 q.err = fmt.Errorf("datastore: invalid order: %q", field)
510 return q 507 return q
511 } 508 }
512 if len(o.field) == 0 { 509 if len(o.field) == 0 {
513 q.err = errors.New("datastore: empty order") 510 q.err = errors.New("datastore: empty order")
514 return q 511 return q
515 } 512 }
516 q.order = append(q.order, o) 513 q.order = append(q.order, o)
517 return q 514 return q
518 } 515 }
519 516
520 func (q *queryImpl) KeysOnly() gae.DSQuery { 517 func (q *queryImpl) KeysOnly() wrapper.DSQuery {
521 q = q.clone() 518 q = q.clone()
522 q.keysOnly = true 519 q.keysOnly = true
523 return q 520 return q
524 } 521 }
525 522
526 func (q *queryImpl) Limit(limit int) gae.DSQuery { 523 func (q *queryImpl) Limit(limit int) wrapper.DSQuery {
527 q = q.clone() 524 q = q.clone()
528 if limit < math.MinInt32 || limit > math.MaxInt32 { 525 if limit < math.MinInt32 || limit > math.MaxInt32 {
529 q.err = errors.New("datastore: query limit overflow") 526 q.err = errors.New("datastore: query limit overflow")
530 return q 527 return q
531 } 528 }
532 q.limit = int32(limit) 529 q.limit = int32(limit)
533 return q 530 return q
534 } 531 }
535 532
536 func (q *queryImpl) Offset(offset int) gae.DSQuery { 533 func (q *queryImpl) Offset(offset int) wrapper.DSQuery {
537 q = q.clone() 534 q = q.clone()
538 if offset < 0 { 535 if offset < 0 {
539 q.err = errors.New("datastore: negative query offset") 536 q.err = errors.New("datastore: negative query offset")
540 return q 537 return q
541 } 538 }
542 if offset > math.MaxInt32 { 539 if offset > math.MaxInt32 {
543 q.err = errors.New("datastore: query offset overflow") 540 q.err = errors.New("datastore: query offset overflow")
544 return q 541 return q
545 } 542 }
546 q.offset = int32(offset) 543 q.offset = int32(offset)
547 return q 544 return q
548 } 545 }
549 546
550 func (q *queryImpl) Start(c gae.DSCursor) gae.DSQuery { 547 func (q *queryImpl) Start(c wrapper.DSCursor) wrapper.DSQuery {
551 q = q.clone() 548 q = q.clone()
552 curs := c.(queryCursor) 549 curs := c.(queryCursor)
553 if !curs.Valid() { 550 if !curs.Valid() {
554 q.err = errors.New("datastore: invalid cursor") 551 q.err = errors.New("datastore: invalid cursor")
555 return q 552 return q
556 } 553 }
557 q.start = curs 554 q.start = curs
558 return q 555 return q
559 } 556 }
560 557
561 func (q *queryImpl) End(c gae.DSCursor) gae.DSQuery { 558 func (q *queryImpl) End(c wrapper.DSCursor) wrapper.DSQuery {
562 q = q.clone() 559 q = q.clone()
563 curs := c.(queryCursor) 560 curs := c.(queryCursor)
564 if !curs.Valid() { 561 if !curs.Valid() {
565 q.err = errors.New("datastore: invalid cursor") 562 q.err = errors.New("datastore: invalid cursor")
566 return q 563 return q
567 } 564 }
568 q.end = curs 565 q.end = curs
569 return q 566 return q
570 } 567 }
OLDNEW
« no previous file with comments | « go/src/infra/gae/libs/wrapper/memory/datastore_data.go ('k') | go/src/infra/gae/libs/wrapper/memory/datastore_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698