1// Copyright 2014 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package datastore 16 17import ( 18 "context" 19 "encoding/base64" 20 "errors" 21 "fmt" 22 "math" 23 "reflect" 24 "strconv" 25 "strings" 26 27 "cloud.google.com/go/internal/trace" 28 wrapperspb "github.com/golang/protobuf/ptypes/wrappers" 29 "google.golang.org/api/iterator" 30 pb "google.golang.org/genproto/googleapis/datastore/v1" 31) 32 33type operator int 34 35const ( 36 lessThan operator = iota + 1 37 lessEq 38 equal 39 greaterEq 40 greaterThan 41 42 keyFieldName = "__key__" 43) 44 45var operatorToProto = map[operator]pb.PropertyFilter_Operator{ 46 lessThan: pb.PropertyFilter_LESS_THAN, 47 lessEq: pb.PropertyFilter_LESS_THAN_OR_EQUAL, 48 equal: pb.PropertyFilter_EQUAL, 49 greaterEq: pb.PropertyFilter_GREATER_THAN_OR_EQUAL, 50 greaterThan: pb.PropertyFilter_GREATER_THAN, 51} 52 53// filter is a conditional filter on query results. 54type filter struct { 55 FieldName string 56 Op operator 57 Value interface{} 58} 59 60type sortDirection bool 61 62const ( 63 ascending sortDirection = false 64 descending sortDirection = true 65) 66 67var sortDirectionToProto = map[sortDirection]pb.PropertyOrder_Direction{ 68 ascending: pb.PropertyOrder_ASCENDING, 69 descending: pb.PropertyOrder_DESCENDING, 70} 71 72// order is a sort order on query results. 73type order struct { 74 FieldName string 75 Direction sortDirection 76} 77 78// NewQuery creates a new Query for a specific entity kind. 79// 80// An empty kind means to return all entities, including entities created and 81// managed by other App Engine features, and is called a kindless query. 82// Kindless queries cannot include filters or sort orders on property values. 83func NewQuery(kind string) *Query { 84 return &Query{ 85 kind: kind, 86 limit: -1, 87 } 88} 89 90// Query represents a datastore query. 91type Query struct { 92 kind string 93 ancestor *Key 94 filter []filter 95 order []order 96 projection []string 97 98 distinct bool 99 distinctOn []string 100 keysOnly bool 101 eventual bool 102 limit int32 103 offset int32 104 start []byte 105 end []byte 106 107 namespace string 108 109 trans *Transaction 110 111 err error 112} 113 114func (q *Query) clone() *Query { 115 x := *q 116 // Copy the contents of the slice-typed fields to a new backing store. 117 if len(q.filter) > 0 { 118 x.filter = make([]filter, len(q.filter)) 119 copy(x.filter, q.filter) 120 } 121 if len(q.order) > 0 { 122 x.order = make([]order, len(q.order)) 123 copy(x.order, q.order) 124 } 125 return &x 126} 127 128// Ancestor returns a derivative query with an ancestor filter. 129// The ancestor should not be nil. 130func (q *Query) Ancestor(ancestor *Key) *Query { 131 q = q.clone() 132 if ancestor == nil { 133 q.err = errors.New("datastore: nil query ancestor") 134 return q 135 } 136 q.ancestor = ancestor 137 return q 138} 139 140// EventualConsistency returns a derivative query that returns eventually 141// consistent results. 142// It only has an effect on ancestor queries. 143func (q *Query) EventualConsistency() *Query { 144 q = q.clone() 145 q.eventual = true 146 return q 147} 148 149// Namespace returns a derivative query that is associated with the given 150// namespace. 151// 152// A namespace may be used to partition data for multi-tenant applications. 153// For details, see https://cloud.google.com/datastore/docs/concepts/multitenancy. 154func (q *Query) Namespace(ns string) *Query { 155 q = q.clone() 156 q.namespace = ns 157 return q 158} 159 160// Transaction returns a derivative query that is associated with the given 161// transaction. 162// 163// All reads performed as part of the transaction will come from a single 164// consistent snapshot. Furthermore, if the transaction is set to a 165// serializable isolation level, another transaction cannot concurrently modify 166// the data that is read or modified by this transaction. 167func (q *Query) Transaction(t *Transaction) *Query { 168 q = q.clone() 169 q.trans = t 170 return q 171} 172 173// Filter returns a derivative query with a field-based filter. 174// The filterStr argument must be a field name followed by optional space, 175// followed by an operator, one of ">", "<", ">=", "<=", or "=". 176// Fields are compared against the provided value using the operator. 177// Multiple filters are AND'ed together. 178// Field names which contain spaces, quote marks, or operator characters 179// should be passed as quoted Go string literals as returned by strconv.Quote 180// or the fmt package's %q verb. 181func (q *Query) Filter(filterStr string, value interface{}) *Query { 182 q = q.clone() 183 filterStr = strings.TrimSpace(filterStr) 184 if filterStr == "" { 185 q.err = fmt.Errorf("datastore: invalid filter %q", filterStr) 186 return q 187 } 188 f := filter{ 189 FieldName: strings.TrimRight(filterStr, " ><=!"), 190 Value: value, 191 } 192 switch op := strings.TrimSpace(filterStr[len(f.FieldName):]); op { 193 case "<=": 194 f.Op = lessEq 195 case ">=": 196 f.Op = greaterEq 197 case "<": 198 f.Op = lessThan 199 case ">": 200 f.Op = greaterThan 201 case "=": 202 f.Op = equal 203 default: 204 q.err = fmt.Errorf("datastore: invalid operator %q in filter %q", op, filterStr) 205 return q 206 } 207 var err error 208 f.FieldName, err = unquote(f.FieldName) 209 if err != nil { 210 q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", f.FieldName) 211 return q 212 } 213 q.filter = append(q.filter, f) 214 return q 215} 216 217// Order returns a derivative query with a field-based sort order. Orders are 218// applied in the order they are added. The default order is ascending; to sort 219// in descending order prefix the fieldName with a minus sign (-). 220// Field names which contain spaces, quote marks, or the minus sign 221// should be passed as quoted Go string literals as returned by strconv.Quote 222// or the fmt package's %q verb. 223func (q *Query) Order(fieldName string) *Query { 224 q = q.clone() 225 fieldName, dir := strings.TrimSpace(fieldName), ascending 226 if strings.HasPrefix(fieldName, "-") { 227 fieldName, dir = strings.TrimSpace(fieldName[1:]), descending 228 } else if strings.HasPrefix(fieldName, "+") { 229 q.err = fmt.Errorf("datastore: invalid order: %q", fieldName) 230 return q 231 } 232 fieldName, err := unquote(fieldName) 233 if err != nil { 234 q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", fieldName) 235 return q 236 } 237 if fieldName == "" { 238 q.err = errors.New("datastore: empty order") 239 return q 240 } 241 q.order = append(q.order, order{ 242 Direction: dir, 243 FieldName: fieldName, 244 }) 245 return q 246} 247 248// unquote optionally interprets s as a double-quoted or backquoted Go 249// string literal if it begins with the relevant character. 250func unquote(s string) (string, error) { 251 if s == "" || (s[0] != '`' && s[0] != '"') { 252 return s, nil 253 } 254 return strconv.Unquote(s) 255} 256 257// Project returns a derivative query that yields only the given fields. It 258// cannot be used with KeysOnly. 259func (q *Query) Project(fieldNames ...string) *Query { 260 q = q.clone() 261 q.projection = append([]string(nil), fieldNames...) 262 return q 263} 264 265// Distinct returns a derivative query that yields de-duplicated entities with 266// respect to the set of projected fields. It is only used for projection 267// queries. Distinct cannot be used with DistinctOn. 268func (q *Query) Distinct() *Query { 269 q = q.clone() 270 q.distinct = true 271 return q 272} 273 274// DistinctOn returns a derivative query that yields de-duplicated entities with 275// respect to the set of the specified fields. It is only used for projection 276// queries. The field list should be a subset of the projected field list. 277// DistinctOn cannot be used with Distinct. 278func (q *Query) DistinctOn(fieldNames ...string) *Query { 279 q = q.clone() 280 q.distinctOn = fieldNames 281 return q 282} 283 284// KeysOnly returns a derivative query that yields only keys, not keys and 285// entities. It cannot be used with projection queries. 286func (q *Query) KeysOnly() *Query { 287 q = q.clone() 288 q.keysOnly = true 289 return q 290} 291 292// Limit returns a derivative query that has a limit on the number of results 293// returned. A negative value means unlimited. 294func (q *Query) Limit(limit int) *Query { 295 q = q.clone() 296 if limit < math.MinInt32 || limit > math.MaxInt32 { 297 q.err = errors.New("datastore: query limit overflow") 298 return q 299 } 300 q.limit = int32(limit) 301 return q 302} 303 304// Offset returns a derivative query that has an offset of how many keys to 305// skip over before returning results. A negative value is invalid. 306func (q *Query) Offset(offset int) *Query { 307 q = q.clone() 308 if offset < 0 { 309 q.err = errors.New("datastore: negative query offset") 310 return q 311 } 312 if offset > math.MaxInt32 { 313 q.err = errors.New("datastore: query offset overflow") 314 return q 315 } 316 q.offset = int32(offset) 317 return q 318} 319 320// Start returns a derivative query with the given start point. 321func (q *Query) Start(c Cursor) *Query { 322 q = q.clone() 323 q.start = c.cc 324 return q 325} 326 327// End returns a derivative query with the given end point. 328func (q *Query) End(c Cursor) *Query { 329 q = q.clone() 330 q.end = c.cc 331 return q 332} 333 334// toProto converts the query to a protocol buffer. 335func (q *Query) toProto(req *pb.RunQueryRequest) error { 336 if len(q.projection) != 0 && q.keysOnly { 337 return errors.New("datastore: query cannot both project and be keys-only") 338 } 339 if len(q.distinctOn) != 0 && q.distinct { 340 return errors.New("datastore: query cannot be both distinct and distinct-on") 341 } 342 dst := &pb.Query{} 343 if q.kind != "" { 344 dst.Kind = []*pb.KindExpression{{Name: q.kind}} 345 } 346 if q.projection != nil { 347 for _, propertyName := range q.projection { 348 dst.Projection = append(dst.Projection, &pb.Projection{Property: &pb.PropertyReference{Name: propertyName}}) 349 } 350 351 for _, propertyName := range q.distinctOn { 352 dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName}) 353 } 354 355 if q.distinct { 356 for _, propertyName := range q.projection { 357 dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName}) 358 } 359 } 360 } 361 if q.keysOnly { 362 dst.Projection = []*pb.Projection{{Property: &pb.PropertyReference{Name: keyFieldName}}} 363 } 364 365 var filters []*pb.Filter 366 for _, qf := range q.filter { 367 if qf.FieldName == "" { 368 return errors.New("datastore: empty query filter field name") 369 } 370 v, err := interfaceToProto(reflect.ValueOf(qf.Value).Interface(), false) 371 if err != nil { 372 return fmt.Errorf("datastore: bad query filter value type: %v", err) 373 } 374 op, ok := operatorToProto[qf.Op] 375 if !ok { 376 return errors.New("datastore: unknown query filter operator") 377 } 378 xf := &pb.PropertyFilter{ 379 Op: op, 380 Property: &pb.PropertyReference{Name: qf.FieldName}, 381 Value: v, 382 } 383 filters = append(filters, &pb.Filter{ 384 FilterType: &pb.Filter_PropertyFilter{PropertyFilter: xf}, 385 }) 386 } 387 388 if q.ancestor != nil { 389 filters = append(filters, &pb.Filter{ 390 FilterType: &pb.Filter_PropertyFilter{PropertyFilter: &pb.PropertyFilter{ 391 Property: &pb.PropertyReference{Name: keyFieldName}, 392 Op: pb.PropertyFilter_HAS_ANCESTOR, 393 Value: &pb.Value{ValueType: &pb.Value_KeyValue{KeyValue: keyToProto(q.ancestor)}}, 394 }}}) 395 } 396 397 if len(filters) == 1 { 398 dst.Filter = filters[0] 399 } else if len(filters) > 1 { 400 dst.Filter = &pb.Filter{FilterType: &pb.Filter_CompositeFilter{CompositeFilter: &pb.CompositeFilter{ 401 Op: pb.CompositeFilter_AND, 402 Filters: filters, 403 }}} 404 } 405 406 for _, qo := range q.order { 407 if qo.FieldName == "" { 408 return errors.New("datastore: empty query order field name") 409 } 410 xo := &pb.PropertyOrder{ 411 Property: &pb.PropertyReference{Name: qo.FieldName}, 412 Direction: sortDirectionToProto[qo.Direction], 413 } 414 dst.Order = append(dst.Order, xo) 415 } 416 if q.limit >= 0 { 417 dst.Limit = &wrapperspb.Int32Value{Value: q.limit} 418 } 419 dst.Offset = q.offset 420 dst.StartCursor = q.start 421 dst.EndCursor = q.end 422 423 if t := q.trans; t != nil { 424 if t.id == nil { 425 return errExpiredTransaction 426 } 427 if q.eventual { 428 return errors.New("datastore: cannot use EventualConsistency query in a transaction") 429 } 430 req.ReadOptions = &pb.ReadOptions{ 431 ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id}, 432 } 433 } 434 435 if q.eventual { 436 req.ReadOptions = &pb.ReadOptions{ConsistencyType: &pb.ReadOptions_ReadConsistency_{ReadConsistency: pb.ReadOptions_EVENTUAL}} 437 } 438 439 req.QueryType = &pb.RunQueryRequest_Query{Query: dst} 440 return nil 441} 442 443// Count returns the number of results for the given query. 444// 445// The running time and number of API calls made by Count scale linearly with 446// the sum of the query's offset and limit. Unless the result count is 447// expected to be small, it is best to specify a limit; otherwise Count will 448// continue until it finishes counting or the provided context expires. 449func (c *Client) Count(ctx context.Context, q *Query) (n int, err error) { 450 ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.Count") 451 defer func() { trace.EndSpan(ctx, err) }() 452 453 // Check that the query is well-formed. 454 if q.err != nil { 455 return 0, q.err 456 } 457 458 // Create a copy of the query, with keysOnly true (if we're not a projection, 459 // since the two are incompatible). 460 newQ := q.clone() 461 newQ.keysOnly = len(newQ.projection) == 0 462 463 // Create an iterator and use it to walk through the batches of results 464 // directly. 465 it := c.Run(ctx, newQ) 466 for { 467 err := it.nextBatch() 468 if err == iterator.Done { 469 return n, nil 470 } 471 if err != nil { 472 return 0, err 473 } 474 n += len(it.results) 475 } 476} 477 478// GetAll runs the provided query in the given context and returns all keys 479// that match that query, as well as appending the values to dst. 480// 481// dst must have type *[]S or *[]*S or *[]P, for some struct type S or some non- 482// interface, non-pointer type P such that P or *P implements PropertyLoadSaver. 483// 484// As a special case, *PropertyList is an invalid type for dst, even though a 485// PropertyList is a slice of structs. It is treated as invalid to avoid being 486// mistakenly passed when *[]PropertyList was intended. 487// 488// The keys returned by GetAll will be in a 1-1 correspondence with the entities 489// added to dst. 490// 491// If q is a ``keys-only'' query, GetAll ignores dst and only returns the keys. 492// 493// The running time and number of API calls made by GetAll scale linearly with 494// with the sum of the query's offset and limit. Unless the result count is 495// expected to be small, it is best to specify a limit; otherwise GetAll will 496// continue until it finishes collecting results or the provided context 497// expires. 498func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) (keys []*Key, err error) { 499 ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.GetAll") 500 defer func() { trace.EndSpan(ctx, err) }() 501 502 var ( 503 dv reflect.Value 504 mat multiArgType 505 elemType reflect.Type 506 errFieldMismatch error 507 ) 508 if !q.keysOnly { 509 dv = reflect.ValueOf(dst) 510 if dv.Kind() != reflect.Ptr || dv.IsNil() { 511 return nil, ErrInvalidEntityType 512 } 513 dv = dv.Elem() 514 mat, elemType = checkMultiArg(dv) 515 if mat == multiArgTypeInvalid || mat == multiArgTypeInterface { 516 return nil, ErrInvalidEntityType 517 } 518 } 519 520 for t := c.Run(ctx, q); ; { 521 k, e, err := t.next() 522 if err == iterator.Done { 523 break 524 } 525 if err != nil { 526 return keys, err 527 } 528 if !q.keysOnly { 529 ev := reflect.New(elemType) 530 if elemType.Kind() == reflect.Map { 531 // This is a special case. The zero values of a map type are 532 // not immediately useful; they have to be make'd. 533 // 534 // Funcs and channels are similar, in that a zero value is not useful, 535 // but even a freshly make'd channel isn't useful: there's no fixed 536 // channel buffer size that is always going to be large enough, and 537 // there's no goroutine to drain the other end. Theoretically, these 538 // types could be supported, for example by sniffing for a constructor 539 // method or requiring prior registration, but for now it's not a 540 // frequent enough concern to be worth it. Programmers can work around 541 // it by explicitly using Iterator.Next instead of the Query.GetAll 542 // convenience method. 543 x := reflect.MakeMap(elemType) 544 ev.Elem().Set(x) 545 } 546 if err = loadEntityProto(ev.Interface(), e); err != nil { 547 if _, ok := err.(*ErrFieldMismatch); ok { 548 // We continue loading entities even in the face of field mismatch errors. 549 // If we encounter any other error, that other error is returned. Otherwise, 550 // an ErrFieldMismatch is returned. 551 errFieldMismatch = err 552 } else { 553 return keys, err 554 } 555 } 556 if mat != multiArgTypeStructPtr { 557 ev = ev.Elem() 558 } 559 dv.Set(reflect.Append(dv, ev)) 560 } 561 keys = append(keys, k) 562 } 563 return keys, errFieldMismatch 564} 565 566// Run runs the given query in the given context. 567func (c *Client) Run(ctx context.Context, q *Query) *Iterator { 568 if q.err != nil { 569 return &Iterator{err: q.err} 570 } 571 t := &Iterator{ 572 ctx: ctx, 573 client: c, 574 limit: q.limit, 575 offset: q.offset, 576 keysOnly: q.keysOnly, 577 pageCursor: q.start, 578 entityCursor: q.start, 579 req: &pb.RunQueryRequest{ 580 ProjectId: c.dataset, 581 }, 582 } 583 584 if q.namespace != "" { 585 t.req.PartitionId = &pb.PartitionId{ 586 NamespaceId: q.namespace, 587 } 588 } 589 590 if err := q.toProto(t.req); err != nil { 591 t.err = err 592 } 593 return t 594} 595 596// Iterator is the result of running a query. 597// 598// It is not safe for concurrent use. 599type Iterator struct { 600 ctx context.Context 601 client *Client 602 err error 603 604 // results is the list of EntityResults still to be iterated over from the 605 // most recent API call. It will be nil if no requests have yet been issued. 606 results []*pb.EntityResult 607 // req is the request to send. It may be modified and used multiple times. 608 req *pb.RunQueryRequest 609 610 // limit is the limit on the number of results this iterator should return. 611 // The zero value is used to prevent further fetches from the server. 612 // A negative value means unlimited. 613 limit int32 614 // offset is the number of results that still need to be skipped. 615 offset int32 616 // keysOnly records whether the query was keys-only (skip entity loading). 617 keysOnly bool 618 619 // pageCursor is the compiled cursor for the next batch/page of result. 620 // TODO(djd): Can we delete this in favour of paging with the last 621 // entityCursor from each batch? 622 pageCursor []byte 623 // entityCursor is the compiled cursor of the next result. 624 entityCursor []byte 625} 626 627// Next returns the key of the next result. When there are no more results, 628// iterator.Done is returned as the error. 629// 630// If the query is not keys only and dst is non-nil, it also loads the entity 631// stored for that key into the struct pointer or PropertyLoadSaver dst, with 632// the same semantics and possible errors as for the Get function. 633func (t *Iterator) Next(dst interface{}) (k *Key, err error) { 634 k, e, err := t.next() 635 if err != nil { 636 return nil, err 637 } 638 if dst != nil && !t.keysOnly { 639 err = loadEntityProto(dst, e) 640 } 641 return k, err 642} 643 644func (t *Iterator) next() (*Key, *pb.Entity, error) { 645 // Fetch additional batches while there are no more results. 646 for t.err == nil && len(t.results) == 0 { 647 t.err = t.nextBatch() 648 } 649 if t.err != nil { 650 return nil, nil, t.err 651 } 652 653 // Extract the next result, update cursors, and parse the entity's key. 654 e := t.results[0] 655 t.results = t.results[1:] 656 t.entityCursor = e.Cursor 657 if len(t.results) == 0 { 658 t.entityCursor = t.pageCursor // At the end of the batch. 659 } 660 if e.Entity.Key == nil { 661 return nil, nil, errors.New("datastore: internal error: server did not return a key") 662 } 663 k, err := protoToKey(e.Entity.Key) 664 if err != nil || k.Incomplete() { 665 return nil, nil, errors.New("datastore: internal error: server returned an invalid key") 666 } 667 668 return k, e.Entity, nil 669} 670 671// nextBatch makes a single call to the server for a batch of results. 672func (t *Iterator) nextBatch() error { 673 if t.err != nil { 674 return t.err 675 } 676 677 if t.limit == 0 { 678 return iterator.Done // Short-circuits the zero-item response. 679 } 680 681 // Adjust the query with the latest start cursor, limit and offset. 682 q := t.req.GetQuery() 683 q.StartCursor = t.pageCursor 684 q.Offset = t.offset 685 if t.limit >= 0 { 686 q.Limit = &wrapperspb.Int32Value{Value: t.limit} 687 } else { 688 q.Limit = nil 689 } 690 691 // Run the query. 692 resp, err := t.client.client.RunQuery(t.ctx, t.req) 693 if err != nil { 694 return err 695 } 696 697 // Adjust any offset from skipped results. 698 skip := resp.Batch.SkippedResults 699 if skip < 0 { 700 return errors.New("datastore: internal error: negative number of skipped_results") 701 } 702 t.offset -= skip 703 if t.offset < 0 { 704 return errors.New("datastore: internal error: query skipped too many results") 705 } 706 if t.offset > 0 && len(resp.Batch.EntityResults) > 0 { 707 return errors.New("datastore: internal error: query returned results before requested offset") 708 } 709 710 // Adjust the limit. 711 if t.limit >= 0 { 712 t.limit -= int32(len(resp.Batch.EntityResults)) 713 if t.limit < 0 { 714 return errors.New("datastore: internal error: query returned more results than the limit") 715 } 716 } 717 718 // If there are no more results available, set limit to zero to prevent 719 // further fetches. Otherwise, check that there is a next page cursor available. 720 if resp.Batch.MoreResults != pb.QueryResultBatch_NOT_FINISHED { 721 t.limit = 0 722 } else if resp.Batch.EndCursor == nil { 723 return errors.New("datastore: internal error: server did not return a cursor") 724 } 725 726 // Update cursors. 727 // If any results were skipped, use the SkippedCursor as the next entity cursor. 728 if skip > 0 { 729 t.entityCursor = resp.Batch.SkippedCursor 730 } else { 731 t.entityCursor = q.StartCursor 732 } 733 t.pageCursor = resp.Batch.EndCursor 734 735 t.results = resp.Batch.EntityResults 736 return nil 737} 738 739// Cursor returns a cursor for the iterator's current location. 740func (t *Iterator) Cursor() (c Cursor, err error) { 741 t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Query.Cursor") 742 defer func() { trace.EndSpan(t.ctx, err) }() 743 744 // If there is still an offset, we need to the skip those results first. 745 for t.err == nil && t.offset > 0 { 746 t.err = t.nextBatch() 747 } 748 749 if t.err != nil && t.err != iterator.Done { 750 return Cursor{}, t.err 751 } 752 753 return Cursor{t.entityCursor}, nil 754} 755 756// Cursor is an iterator's position. It can be converted to and from an opaque 757// string. A cursor can be used from different HTTP requests, but only with a 758// query with the same kind, ancestor, filter and order constraints. 759// 760// The zero Cursor can be used to indicate that there is no start and/or end 761// constraint for a query. 762type Cursor struct { 763 cc []byte 764} 765 766// String returns a base-64 string representation of a cursor. 767func (c Cursor) String() string { 768 if c.cc == nil { 769 return "" 770 } 771 772 return strings.TrimRight(base64.URLEncoding.EncodeToString(c.cc), "=") 773} 774 775// DecodeCursor decodes a cursor from its base-64 string representation. 776func DecodeCursor(s string) (Cursor, error) { 777 if s == "" { 778 return Cursor{}, nil 779 } 780 if n := len(s) % 4; n != 0 { 781 s += strings.Repeat("=", 4-n) 782 } 783 b, err := base64.URLEncoding.DecodeString(s) 784 if err != nil { 785 return Cursor{}, err 786 } 787 return Cursor{b}, nil 788} 789