|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784 |
- // Copyright 2014 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package datastore
-
- import (
- "encoding/base64"
- "errors"
- "fmt"
- "math"
- "reflect"
- "strconv"
- "strings"
-
- "cloud.google.com/go/internal/trace"
- wrapperspb "github.com/golang/protobuf/ptypes/wrappers"
- "golang.org/x/net/context"
- "google.golang.org/api/iterator"
- pb "google.golang.org/genproto/googleapis/datastore/v1"
- )
-
- type operator int
-
- const (
- lessThan operator = iota + 1
- lessEq
- equal
- greaterEq
- greaterThan
-
- keyFieldName = "__key__"
- )
-
- var operatorToProto = map[operator]pb.PropertyFilter_Operator{
- lessThan: pb.PropertyFilter_LESS_THAN,
- lessEq: pb.PropertyFilter_LESS_THAN_OR_EQUAL,
- equal: pb.PropertyFilter_EQUAL,
- greaterEq: pb.PropertyFilter_GREATER_THAN_OR_EQUAL,
- greaterThan: pb.PropertyFilter_GREATER_THAN,
- }
-
- // filter is a conditional filter on query results.
- type filter struct {
- FieldName string
- Op operator
- Value interface{}
- }
-
- type sortDirection bool
-
- const (
- ascending sortDirection = false
- descending sortDirection = true
- )
-
- var sortDirectionToProto = map[sortDirection]pb.PropertyOrder_Direction{
- ascending: pb.PropertyOrder_ASCENDING,
- descending: pb.PropertyOrder_DESCENDING,
- }
-
- // order is a sort order on query results.
- type order struct {
- FieldName string
- Direction sortDirection
- }
-
- // NewQuery creates a new Query for a specific entity kind.
- //
- // An empty kind means to return all entities, including entities created and
- // managed by other App Engine features, and is called a kindless query.
- // Kindless queries cannot include filters or sort orders on property values.
- func NewQuery(kind string) *Query {
- return &Query{
- kind: kind,
- limit: -1,
- }
- }
-
- // Query represents a datastore query.
- type Query struct {
- kind string
- ancestor *Key
- filter []filter
- order []order
- projection []string
-
- distinct bool
- distinctOn []string
- keysOnly bool
- eventual bool
- limit int32
- offset int32
- start []byte
- end []byte
-
- namespace string
-
- trans *Transaction
-
- err error
- }
-
- func (q *Query) clone() *Query {
- x := *q
- // Copy the contents of the slice-typed fields to a new backing store.
- if len(q.filter) > 0 {
- x.filter = make([]filter, len(q.filter))
- copy(x.filter, q.filter)
- }
- if len(q.order) > 0 {
- x.order = make([]order, len(q.order))
- copy(x.order, q.order)
- }
- return &x
- }
-
- // Ancestor returns a derivative query with an ancestor filter.
- // The ancestor should not be nil.
- func (q *Query) Ancestor(ancestor *Key) *Query {
- q = q.clone()
- if ancestor == nil {
- q.err = errors.New("datastore: nil query ancestor")
- return q
- }
- q.ancestor = ancestor
- return q
- }
-
- // EventualConsistency returns a derivative query that returns eventually
- // consistent results.
- // It only has an effect on ancestor queries.
- func (q *Query) EventualConsistency() *Query {
- q = q.clone()
- q.eventual = true
- return q
- }
-
- // Namespace returns a derivative query that is associated with the given
- // namespace.
- //
- // A namespace may be used to partition data for multi-tenant applications.
- // For details, see https://cloud.google.com/datastore/docs/concepts/multitenancy.
- func (q *Query) Namespace(ns string) *Query {
- q = q.clone()
- q.namespace = ns
- return q
- }
-
- // Transaction returns a derivative query that is associated with the given
- // transaction.
- //
- // All reads performed as part of the transaction will come from a single
- // consistent snapshot. Furthermore, if the transaction is set to a
- // serializable isolation level, another transaction cannot concurrently modify
- // the data that is read or modified by this transaction.
- func (q *Query) Transaction(t *Transaction) *Query {
- q = q.clone()
- q.trans = t
- return q
- }
-
- // Filter returns a derivative query with a field-based filter.
- // The filterStr argument must be a field name followed by optional space,
- // followed by an operator, one of ">", "<", ">=", "<=", or "=".
- // Fields are compared against the provided value using the operator.
- // Multiple filters are AND'ed together.
- // Field names which contain spaces, quote marks, or operator characters
- // should be passed as quoted Go string literals as returned by strconv.Quote
- // or the fmt package's %q verb.
- func (q *Query) Filter(filterStr string, value interface{}) *Query {
- q = q.clone()
- filterStr = strings.TrimSpace(filterStr)
- if filterStr == "" {
- q.err = fmt.Errorf("datastore: invalid filter %q", filterStr)
- return q
- }
- f := filter{
- FieldName: strings.TrimRight(filterStr, " ><=!"),
- Value: value,
- }
- switch op := strings.TrimSpace(filterStr[len(f.FieldName):]); op {
- case "<=":
- f.Op = lessEq
- case ">=":
- f.Op = greaterEq
- case "<":
- f.Op = lessThan
- case ">":
- f.Op = greaterThan
- case "=":
- f.Op = equal
- default:
- q.err = fmt.Errorf("datastore: invalid operator %q in filter %q", op, filterStr)
- return q
- }
- var err error
- f.FieldName, err = unquote(f.FieldName)
- if err != nil {
- q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", f.FieldName)
- return q
- }
- q.filter = append(q.filter, f)
- return q
- }
-
- // Order returns a derivative query with a field-based sort order. Orders are
- // applied in the order they are added. The default order is ascending; to sort
- // in descending order prefix the fieldName with a minus sign (-).
- // Field names which contain spaces, quote marks, or the minus sign
- // should be passed as quoted Go string literals as returned by strconv.Quote
- // or the fmt package's %q verb.
- func (q *Query) Order(fieldName string) *Query {
- q = q.clone()
- fieldName, dir := strings.TrimSpace(fieldName), ascending
- if strings.HasPrefix(fieldName, "-") {
- fieldName, dir = strings.TrimSpace(fieldName[1:]), descending
- } else if strings.HasPrefix(fieldName, "+") {
- q.err = fmt.Errorf("datastore: invalid order: %q", fieldName)
- return q
- }
- fieldName, err := unquote(fieldName)
- if err != nil {
- q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", fieldName)
- return q
- }
- if fieldName == "" {
- q.err = errors.New("datastore: empty order")
- return q
- }
- q.order = append(q.order, order{
- Direction: dir,
- FieldName: fieldName,
- })
- return q
- }
-
- // unquote optionally interprets s as a double-quoted or backquoted Go
- // string literal if it begins with the relevant character.
- func unquote(s string) (string, error) {
- if s == "" || (s[0] != '`' && s[0] != '"') {
- return s, nil
- }
- return strconv.Unquote(s)
- }
-
- // Project returns a derivative query that yields only the given fields. It
- // cannot be used with KeysOnly.
- func (q *Query) Project(fieldNames ...string) *Query {
- q = q.clone()
- q.projection = append([]string(nil), fieldNames...)
- return q
- }
-
- // Distinct returns a derivative query that yields de-duplicated entities with
- // respect to the set of projected fields. It is only used for projection
- // queries. Distinct cannot be used with DistinctOn.
- func (q *Query) Distinct() *Query {
- q = q.clone()
- q.distinct = true
- return q
- }
-
- // DistinctOn returns a derivative query that yields de-duplicated entities with
- // respect to the set of the specified fields. It is only used for projection
- // queries. The field list should be a subset of the projected field list.
- // DistinctOn cannot be used with Distinct.
- func (q *Query) DistinctOn(fieldNames ...string) *Query {
- q = q.clone()
- q.distinctOn = fieldNames
- return q
- }
-
- // KeysOnly returns a derivative query that yields only keys, not keys and
- // entities. It cannot be used with projection queries.
- func (q *Query) KeysOnly() *Query {
- q = q.clone()
- q.keysOnly = true
- return q
- }
-
- // Limit returns a derivative query that has a limit on the number of results
- // returned. A negative value means unlimited.
- func (q *Query) Limit(limit int) *Query {
- q = q.clone()
- if limit < math.MinInt32 || limit > math.MaxInt32 {
- q.err = errors.New("datastore: query limit overflow")
- return q
- }
- q.limit = int32(limit)
- return q
- }
-
- // Offset returns a derivative query that has an offset of how many keys to
- // skip over before returning results. A negative value is invalid.
- func (q *Query) Offset(offset int) *Query {
- q = q.clone()
- if offset < 0 {
- q.err = errors.New("datastore: negative query offset")
- return q
- }
- if offset > math.MaxInt32 {
- q.err = errors.New("datastore: query offset overflow")
- return q
- }
- q.offset = int32(offset)
- return q
- }
-
- // Start returns a derivative query with the given start point.
- func (q *Query) Start(c Cursor) *Query {
- q = q.clone()
- q.start = c.cc
- return q
- }
-
- // End returns a derivative query with the given end point.
- func (q *Query) End(c Cursor) *Query {
- q = q.clone()
- q.end = c.cc
- return q
- }
-
- // toProto converts the query to a protocol buffer.
- func (q *Query) toProto(req *pb.RunQueryRequest) error {
- if len(q.projection) != 0 && q.keysOnly {
- return errors.New("datastore: query cannot both project and be keys-only")
- }
- if len(q.distinctOn) != 0 && q.distinct {
- return errors.New("datastore: query cannot be both distinct and distinct-on")
- }
- dst := &pb.Query{}
- if q.kind != "" {
- dst.Kind = []*pb.KindExpression{{Name: q.kind}}
- }
- if q.projection != nil {
- for _, propertyName := range q.projection {
- dst.Projection = append(dst.Projection, &pb.Projection{Property: &pb.PropertyReference{Name: propertyName}})
- }
-
- for _, propertyName := range q.distinctOn {
- dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName})
- }
-
- if q.distinct {
- for _, propertyName := range q.projection {
- dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName})
- }
- }
- }
- if q.keysOnly {
- dst.Projection = []*pb.Projection{{Property: &pb.PropertyReference{Name: keyFieldName}}}
- }
-
- var filters []*pb.Filter
- for _, qf := range q.filter {
- if qf.FieldName == "" {
- return errors.New("datastore: empty query filter field name")
- }
- v, err := interfaceToProto(reflect.ValueOf(qf.Value).Interface(), false)
- if err != nil {
- return fmt.Errorf("datastore: bad query filter value type: %v", err)
- }
- op, ok := operatorToProto[qf.Op]
- if !ok {
- return errors.New("datastore: unknown query filter operator")
- }
- xf := &pb.PropertyFilter{
- Op: op,
- Property: &pb.PropertyReference{Name: qf.FieldName},
- Value: v,
- }
- filters = append(filters, &pb.Filter{
- FilterType: &pb.Filter_PropertyFilter{PropertyFilter: xf},
- })
- }
-
- if q.ancestor != nil {
- filters = append(filters, &pb.Filter{
- FilterType: &pb.Filter_PropertyFilter{PropertyFilter: &pb.PropertyFilter{
- Property: &pb.PropertyReference{Name: keyFieldName},
- Op: pb.PropertyFilter_HAS_ANCESTOR,
- Value: &pb.Value{ValueType: &pb.Value_KeyValue{KeyValue: keyToProto(q.ancestor)}},
- }}})
- }
-
- if len(filters) == 1 {
- dst.Filter = filters[0]
- } else if len(filters) > 1 {
- dst.Filter = &pb.Filter{FilterType: &pb.Filter_CompositeFilter{CompositeFilter: &pb.CompositeFilter{
- Op: pb.CompositeFilter_AND,
- Filters: filters,
- }}}
- }
-
- for _, qo := range q.order {
- if qo.FieldName == "" {
- return errors.New("datastore: empty query order field name")
- }
- xo := &pb.PropertyOrder{
- Property: &pb.PropertyReference{Name: qo.FieldName},
- Direction: sortDirectionToProto[qo.Direction],
- }
- dst.Order = append(dst.Order, xo)
- }
- if q.limit >= 0 {
- dst.Limit = &wrapperspb.Int32Value{Value: q.limit}
- }
- dst.Offset = q.offset
- dst.StartCursor = q.start
- dst.EndCursor = q.end
-
- if t := q.trans; t != nil {
- if t.id == nil {
- return errExpiredTransaction
- }
- if q.eventual {
- return errors.New("datastore: cannot use EventualConsistency query in a transaction")
- }
- req.ReadOptions = &pb.ReadOptions{
- ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
- }
- }
-
- if q.eventual {
- req.ReadOptions = &pb.ReadOptions{ConsistencyType: &pb.ReadOptions_ReadConsistency_{ReadConsistency: pb.ReadOptions_EVENTUAL}}
- }
-
- req.QueryType = &pb.RunQueryRequest_Query{Query: dst}
- return nil
- }
-
- // Count returns the number of results for the given query.
- //
- // The running time and number of API calls made by Count scale linearly with
- // the sum of the query's offset and limit. Unless the result count is
- // expected to be small, it is best to specify a limit; otherwise Count will
- // continue until it finishes counting or the provided context expires.
- func (c *Client) Count(ctx context.Context, q *Query) (n int, err error) {
- ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.Count")
- defer func() { trace.EndSpan(ctx, err) }()
-
- // Check that the query is well-formed.
- if q.err != nil {
- return 0, q.err
- }
-
- // Create a copy of the query, with keysOnly true (if we're not a projection,
- // since the two are incompatible).
- newQ := q.clone()
- newQ.keysOnly = len(newQ.projection) == 0
-
- // Create an iterator and use it to walk through the batches of results
- // directly.
- it := c.Run(ctx, newQ)
- for {
- err := it.nextBatch()
- if err == iterator.Done {
- return n, nil
- }
- if err != nil {
- return 0, err
- }
- n += len(it.results)
- }
- }
-
- // GetAll runs the provided query in the given context and returns all keys
- // that match that query, as well as appending the values to dst.
- //
- // dst must have type *[]S or *[]*S or *[]P, for some struct type S or some non-
- // interface, non-pointer type P such that P or *P implements PropertyLoadSaver.
- //
- // As a special case, *PropertyList is an invalid type for dst, even though a
- // PropertyList is a slice of structs. It is treated as invalid to avoid being
- // mistakenly passed when *[]PropertyList was intended.
- //
- // The keys returned by GetAll will be in a 1-1 correspondence with the entities
- // added to dst.
- //
- // If q is a ``keys-only'' query, GetAll ignores dst and only returns the keys.
- //
- // The running time and number of API calls made by GetAll scale linearly with
- // with the sum of the query's offset and limit. Unless the result count is
- // expected to be small, it is best to specify a limit; otherwise GetAll will
- // continue until it finishes collecting results or the provided context
- // expires.
- func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) (keys []*Key, err error) {
- ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.GetAll")
- defer func() { trace.EndSpan(ctx, err) }()
-
- var (
- dv reflect.Value
- mat multiArgType
- elemType reflect.Type
- errFieldMismatch error
- )
- if !q.keysOnly {
- dv = reflect.ValueOf(dst)
- if dv.Kind() != reflect.Ptr || dv.IsNil() {
- return nil, ErrInvalidEntityType
- }
- dv = dv.Elem()
- mat, elemType = checkMultiArg(dv)
- if mat == multiArgTypeInvalid || mat == multiArgTypeInterface {
- return nil, ErrInvalidEntityType
- }
- }
-
- for t := c.Run(ctx, q); ; {
- k, e, err := t.next()
- if err == iterator.Done {
- break
- }
- if err != nil {
- return keys, err
- }
- if !q.keysOnly {
- ev := reflect.New(elemType)
- if elemType.Kind() == reflect.Map {
- // This is a special case. The zero values of a map type are
- // not immediately useful; they have to be make'd.
- //
- // Funcs and channels are similar, in that a zero value is not useful,
- // but even a freshly make'd channel isn't useful: there's no fixed
- // channel buffer size that is always going to be large enough, and
- // there's no goroutine to drain the other end. Theoretically, these
- // types could be supported, for example by sniffing for a constructor
- // method or requiring prior registration, but for now it's not a
- // frequent enough concern to be worth it. Programmers can work around
- // it by explicitly using Iterator.Next instead of the Query.GetAll
- // convenience method.
- x := reflect.MakeMap(elemType)
- ev.Elem().Set(x)
- }
- if err = loadEntityProto(ev.Interface(), e); err != nil {
- if _, ok := err.(*ErrFieldMismatch); ok {
- // We continue loading entities even in the face of field mismatch errors.
- // If we encounter any other error, that other error is returned. Otherwise,
- // an ErrFieldMismatch is returned.
- errFieldMismatch = err
- } else {
- return keys, err
- }
- }
- if mat != multiArgTypeStructPtr {
- ev = ev.Elem()
- }
- dv.Set(reflect.Append(dv, ev))
- }
- keys = append(keys, k)
- }
- return keys, errFieldMismatch
- }
-
- // Run runs the given query in the given context.
- func (c *Client) Run(ctx context.Context, q *Query) *Iterator {
- if q.err != nil {
- return &Iterator{err: q.err}
- }
- t := &Iterator{
- ctx: ctx,
- client: c,
- limit: q.limit,
- offset: q.offset,
- keysOnly: q.keysOnly,
- pageCursor: q.start,
- entityCursor: q.start,
- req: &pb.RunQueryRequest{
- ProjectId: c.dataset,
- },
- }
-
- ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Query.Run")
- defer func() { trace.EndSpan(ctx, t.err) }()
- if q.namespace != "" {
- t.req.PartitionId = &pb.PartitionId{
- NamespaceId: q.namespace,
- }
- }
-
- if err := q.toProto(t.req); err != nil {
- t.err = err
- }
- return t
- }
-
- // Iterator is the result of running a query.
- type Iterator struct {
- ctx context.Context
- client *Client
- err error
-
- // results is the list of EntityResults still to be iterated over from the
- // most recent API call. It will be nil if no requests have yet been issued.
- results []*pb.EntityResult
- // req is the request to send. It may be modified and used multiple times.
- req *pb.RunQueryRequest
-
- // limit is the limit on the number of results this iterator should return.
- // The zero value is used to prevent further fetches from the server.
- // A negative value means unlimited.
- limit int32
- // offset is the number of results that still need to be skipped.
- offset int32
- // keysOnly records whether the query was keys-only (skip entity loading).
- keysOnly bool
-
- // pageCursor is the compiled cursor for the next batch/page of result.
- // TODO(djd): Can we delete this in favour of paging with the last
- // entityCursor from each batch?
- pageCursor []byte
- // entityCursor is the compiled cursor of the next result.
- entityCursor []byte
- }
-
- // Next returns the key of the next result. When there are no more results,
- // iterator.Done is returned as the error.
- //
- // If the query is not keys only and dst is non-nil, it also loads the entity
- // stored for that key into the struct pointer or PropertyLoadSaver dst, with
- // the same semantics and possible errors as for the Get function.
- func (t *Iterator) Next(dst interface{}) (k *Key, err error) {
- k, e, err := t.next()
- if err != nil {
- return nil, err
- }
- if dst != nil && !t.keysOnly {
- err = loadEntityProto(dst, e)
- }
- return k, err
- }
-
- func (t *Iterator) next() (*Key, *pb.Entity, error) {
- // Fetch additional batches while there are no more results.
- for t.err == nil && len(t.results) == 0 {
- t.err = t.nextBatch()
- }
- if t.err != nil {
- return nil, nil, t.err
- }
-
- // Extract the next result, update cursors, and parse the entity's key.
- e := t.results[0]
- t.results = t.results[1:]
- t.entityCursor = e.Cursor
- if len(t.results) == 0 {
- t.entityCursor = t.pageCursor // At the end of the batch.
- }
- if e.Entity.Key == nil {
- return nil, nil, errors.New("datastore: internal error: server did not return a key")
- }
- k, err := protoToKey(e.Entity.Key)
- if err != nil || k.Incomplete() {
- return nil, nil, errors.New("datastore: internal error: server returned an invalid key")
- }
-
- return k, e.Entity, nil
- }
-
- // nextBatch makes a single call to the server for a batch of results.
- func (t *Iterator) nextBatch() error {
- if t.limit == 0 {
- return iterator.Done // Short-circuits the zero-item response.
- }
-
- // Adjust the query with the latest start cursor, limit and offset.
- q := t.req.GetQuery()
- q.StartCursor = t.pageCursor
- q.Offset = t.offset
- if t.limit >= 0 {
- q.Limit = &wrapperspb.Int32Value{Value: t.limit}
- } else {
- q.Limit = nil
- }
-
- // Run the query.
- resp, err := t.client.client.RunQuery(t.ctx, t.req)
- if err != nil {
- return err
- }
-
- // Adjust any offset from skipped results.
- skip := resp.Batch.SkippedResults
- if skip < 0 {
- return errors.New("datastore: internal error: negative number of skipped_results")
- }
- t.offset -= skip
- if t.offset < 0 {
- return errors.New("datastore: internal error: query skipped too many results")
- }
- if t.offset > 0 && len(resp.Batch.EntityResults) > 0 {
- return errors.New("datastore: internal error: query returned results before requested offset")
- }
-
- // Adjust the limit.
- if t.limit >= 0 {
- t.limit -= int32(len(resp.Batch.EntityResults))
- if t.limit < 0 {
- return errors.New("datastore: internal error: query returned more results than the limit")
- }
- }
-
- // If there are no more results available, set limit to zero to prevent
- // further fetches. Otherwise, check that there is a next page cursor available.
- if resp.Batch.MoreResults != pb.QueryResultBatch_NOT_FINISHED {
- t.limit = 0
- } else if resp.Batch.EndCursor == nil {
- return errors.New("datastore: internal error: server did not return a cursor")
- }
-
- // Update cursors.
- // If any results were skipped, use the SkippedCursor as the next entity cursor.
- if skip > 0 {
- t.entityCursor = resp.Batch.SkippedCursor
- } else {
- t.entityCursor = q.StartCursor
- }
- t.pageCursor = resp.Batch.EndCursor
-
- t.results = resp.Batch.EntityResults
- return nil
- }
-
- // Cursor returns a cursor for the iterator's current location.
- func (t *Iterator) Cursor() (c Cursor, err error) {
- t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Query.Cursor")
- defer func() { trace.EndSpan(t.ctx, err) }()
-
- // If there is still an offset, we need to the skip those results first.
- for t.err == nil && t.offset > 0 {
- t.err = t.nextBatch()
- }
-
- if t.err != nil && t.err != iterator.Done {
- return Cursor{}, t.err
- }
-
- return Cursor{t.entityCursor}, nil
- }
-
- // Cursor is an iterator's position. It can be converted to and from an opaque
- // string. A cursor can be used from different HTTP requests, but only with a
- // query with the same kind, ancestor, filter and order constraints.
- //
- // The zero Cursor can be used to indicate that there is no start and/or end
- // constraint for a query.
- type Cursor struct {
- cc []byte
- }
-
- // String returns a base-64 string representation of a cursor.
- func (c Cursor) String() string {
- if c.cc == nil {
- return ""
- }
-
- return strings.TrimRight(base64.URLEncoding.EncodeToString(c.cc), "=")
- }
-
- // Decode decodes a cursor from its base-64 string representation.
- func DecodeCursor(s string) (Cursor, error) {
- if s == "" {
- return Cursor{}, nil
- }
- if n := len(s) % 4; n != 0 {
- s += strings.Repeat("=", 4-n)
- }
- b, err := base64.URLEncoding.DecodeString(s)
- if err != nil {
- return Cursor{}, err
- }
- return Cursor{b}, nil
- }
|