| Index: memory/raw_datastore_query.go
|
| diff --git a/memory/raw_datastore_query.go b/memory/raw_datastore_query.go
|
| deleted file mode 100644
|
| index 68172a42be2406c28ad01e670a16658a5335720c..0000000000000000000000000000000000000000
|
| --- a/memory/raw_datastore_query.go
|
| +++ /dev/null
|
| @@ -1,593 +0,0 @@
|
| -// Copyright 2015 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -package memory
|
| -
|
| -import (
|
| - "bytes"
|
| - "errors"
|
| - "fmt"
|
| - "math"
|
| - "strings"
|
| -
|
| - "github.com/luci/gae"
|
| - "github.com/luci/gae/helper"
|
| - "github.com/luci/gkvlite"
|
| - "github.com/luci/luci-go/common/cmpbin"
|
| -)
|
| -
|
| -type qDirection bool
|
| -
|
| -const (
|
| - qASC qDirection = true
|
| - qDEC = false
|
| -)
|
| -
|
| -var builtinQueryPrefix = []byte{0}
|
| -var complexQueryPrefix = []byte{1}
|
| -
|
| -type qSortBy struct {
|
| - prop string
|
| - dir qDirection
|
| -}
|
| -
|
| -func (q qSortBy) WriteBinary(buf *bytes.Buffer) {
|
| - if q.dir == qASC {
|
| - buf.WriteByte(0)
|
| - } else {
|
| - buf.WriteByte(1)
|
| - }
|
| - cmpbin.WriteString(buf, q.prop)
|
| -}
|
| -
|
| -func (q *qSortBy) ReadBinary(buf *bytes.Buffer) error {
|
| - dir, err := buf.ReadByte()
|
| - if err != nil {
|
| - return err
|
| - }
|
| - q.dir = dir == 0
|
| - q.prop, _, err = cmpbin.ReadString(buf)
|
| - return err
|
| -}
|
| -
|
| -type qIndex struct {
|
| - kind string
|
| - ancestor bool
|
| - sortby []qSortBy
|
| -}
|
| -
|
| -func (i *qIndex) Builtin() bool {
|
| - return !i.ancestor && len(i.sortby) <= 1
|
| -}
|
| -
|
| -func (i *qIndex) Less(o *qIndex) bool {
|
| - ibuf, obuf := &bytes.Buffer{}, &bytes.Buffer{}
|
| - i.WriteBinary(ibuf)
|
| - o.WriteBinary(obuf)
|
| - return i.String() < o.String()
|
| -}
|
| -
|
| -// Valid verifies that this qIndex doesn't have duplicate sortBy fields.
|
| -func (i *qIndex) Valid() bool {
|
| - names := map[string]bool{}
|
| - for _, sb := range i.sortby {
|
| - if names[sb.prop] {
|
| - return false
|
| - }
|
| - names[sb.prop] = true
|
| - }
|
| - return true
|
| -}
|
| -
|
| -func (i *qIndex) WriteBinary(buf *bytes.Buffer) {
|
| - // TODO(riannucci): do a Grow call here?
|
| - if i.Builtin() {
|
| - buf.Write(builtinQueryPrefix)
|
| - } else {
|
| - buf.Write(complexQueryPrefix)
|
| - }
|
| - cmpbin.WriteString(buf, i.kind)
|
| - if i.ancestor {
|
| - buf.WriteByte(0)
|
| - } else {
|
| - buf.WriteByte(1)
|
| - }
|
| - cmpbin.WriteUint(buf, uint64(len(i.sortby)))
|
| - for _, sb := range i.sortby {
|
| - sb.WriteBinary(buf)
|
| - }
|
| -}
|
| -
|
| -func (i *qIndex) String() string {
|
| - ret := &bytes.Buffer{}
|
| - if i.Builtin() {
|
| - ret.WriteRune('B')
|
| - } else {
|
| - ret.WriteRune('C')
|
| - }
|
| - ret.WriteRune(':')
|
| - ret.WriteString(i.kind)
|
| - if i.ancestor {
|
| - ret.WriteString("|A")
|
| - }
|
| - for _, sb := range i.sortby {
|
| - ret.WriteRune('/')
|
| - if sb.dir == qDEC {
|
| - ret.WriteRune('-')
|
| - }
|
| - ret.WriteString(sb.prop)
|
| - }
|
| - return ret.String()
|
| -}
|
| -
|
| -func (i *qIndex) ReadBinary(buf *bytes.Buffer) error {
|
| - // discard builtin/complex byte
|
| - _, err := buf.ReadByte()
|
| - if err != nil {
|
| - return err
|
| - }
|
| -
|
| - i.kind, _, err = cmpbin.ReadString(buf)
|
| - if err != nil {
|
| - return err
|
| - }
|
| - anc, err := buf.ReadByte()
|
| - if err != nil {
|
| - return err
|
| - }
|
| - i.ancestor = anc == 1
|
| -
|
| - numSorts, _, err := cmpbin.ReadUint(buf)
|
| - if err != nil {
|
| - return err
|
| - }
|
| - if numSorts > 64 {
|
| - return fmt.Errorf("qIndex.ReadBinary: Got over 64 sort orders: %d", numSorts)
|
| - }
|
| - i.sortby = make([]qSortBy, numSorts)
|
| - for idx := range i.sortby {
|
| - err = (&i.sortby[idx]).ReadBinary(buf)
|
| - if err != nil {
|
| - return err
|
| - }
|
| - }
|
| -
|
| - return nil
|
| -}
|
| -
|
| -type queryOp int
|
| -
|
| -const (
|
| - qInvalid queryOp = iota
|
| - qEqual
|
| - qLessThan
|
| - qLessEq
|
| - qGreaterEq
|
| - qGreaterThan
|
| -)
|
| -
|
| -func (o queryOp) isEQOp() bool {
|
| - return o == qEqual
|
| -}
|
| -
|
| -func (o queryOp) isINEQOp() bool {
|
| - return o >= qLessThan && o <= qGreaterThan
|
| -}
|
| -
|
| -var queryOpMap = map[string]queryOp{
|
| - "=": qEqual,
|
| - "<": qLessThan,
|
| - "<=": qLessEq,
|
| - ">=": qGreaterEq,
|
| - ">": qGreaterThan,
|
| -}
|
| -
|
| -type queryFilter struct {
|
| - field string
|
| - op queryOp
|
| - value interface{}
|
| -}
|
| -
|
| -func parseFilter(f string, v interface{}) (ret queryFilter, err error) {
|
| - toks := strings.SplitN(strings.TrimSpace(f), " ", 2)
|
| - if len(toks) != 2 {
|
| - err = errors.New("datastore: invalid filter: " + f)
|
| - } else {
|
| - op := queryOpMap[toks[1]]
|
| - if op == qInvalid {
|
| - err = fmt.Errorf("datastore: invalid operator %q in filter %q", toks[1], f)
|
| - } else {
|
| - ret.field = toks[0]
|
| - ret.op = op
|
| - ret.value = v
|
| - }
|
| - }
|
| - return
|
| -}
|
| -
|
| -type queryOrder struct {
|
| - field string
|
| - direction qDirection
|
| -}
|
| -
|
| -type queryCursor string
|
| -
|
| -func (q queryCursor) String() string { return string(q) }
|
| -func (q queryCursor) Valid() bool { return q != "" }
|
| -
|
| -type queryImpl struct {
|
| - ns string
|
| -
|
| - kind string
|
| - ancestor gae.DSKey
|
| - filter []queryFilter
|
| - order []queryOrder
|
| - project []string
|
| -
|
| - distinct bool
|
| - eventualConsistency bool
|
| - keysOnly bool
|
| - limit int32
|
| - offset int32
|
| -
|
| - start queryCursor
|
| - end queryCursor
|
| -
|
| - err error
|
| -}
|
| -
|
| -var _ gae.DSQuery = (*queryImpl)(nil)
|
| -
|
| -type queryIterImpl struct {
|
| - idx *queryImpl
|
| -}
|
| -
|
| -var _ gae.RDSIterator = (*queryIterImpl)(nil)
|
| -
|
| -func (q *queryIterImpl) Cursor() (gae.DSCursor, error) {
|
| - if q.idx.err != nil {
|
| - return nil, q.idx.err
|
| - }
|
| - return nil, nil
|
| -}
|
| -
|
| -func (q *queryIterImpl) Next(dst gae.DSPropertyLoadSaver) (gae.DSKey, error) {
|
| - if q.idx.err != nil {
|
| - return nil, q.idx.err
|
| - }
|
| - return nil, nil
|
| -}
|
| -
|
| -func (q *queryImpl) normalize() (ret *queryImpl) {
|
| - // ported from GAE SDK datastore_index.py;Normalize()
|
| - ret = q.clone()
|
| -
|
| - bs := newMemStore()
|
| -
|
| - eqProperties := bs.MakePrivateCollection(nil)
|
| -
|
| - ineqProperties := bs.MakePrivateCollection(nil)
|
| -
|
| - for _, f := range ret.filter {
|
| - // if we supported the IN operator, we would check to see if there were
|
| - // multiple value operands here, but the go SDK doesn't support this.
|
| - if f.op.isEQOp() {
|
| - eqProperties.Set([]byte(f.field), []byte{})
|
| - } else if f.op.isINEQOp() {
|
| - ineqProperties.Set([]byte(f.field), []byte{})
|
| - }
|
| - }
|
| -
|
| - ineqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool {
|
| - eqProperties.Delete(i.Key)
|
| - return true
|
| - })
|
| -
|
| - removeSet := bs.MakePrivateCollection(nil)
|
| - eqProperties.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool {
|
| - removeSet.Set(i.Key, []byte{})
|
| - return true
|
| - })
|
| -
|
| - newOrders := []queryOrder{}
|
| - for _, o := range ret.order {
|
| - if removeSet.Get([]byte(o.field)) == nil {
|
| - removeSet.Set([]byte(o.field), []byte{})
|
| - newOrders = append(newOrders, o)
|
| - }
|
| - }
|
| - ret.order = newOrders
|
| -
|
| - // need to fix ret.filters if we ever support the EXISTS operator and/or
|
| - // projections.
|
| - //
|
| - // newFilters = []
|
| - // for f in ret.filters:
|
| - // if f.op != qExists:
|
| - // newFilters = append(newFilters, f)
|
| - // if !removeSet.Has(f.field):
|
| - // removeSet.InsertNoReplace(f.field)
|
| - // newFilters = append(newFilters, f)
|
| - //
|
| - // so ret.filters == newFilters becuase none of ret.filters has op == qExists
|
| - //
|
| - // then:
|
| - //
|
| - // for prop in ret.project:
|
| - // if !removeSet.Has(prop):
|
| - // removeSet.InsertNoReplace(prop)
|
| - // ... make new EXISTS filters, add them to newFilters ...
|
| - // ret.filters = newFilters
|
| - //
|
| - // However, since we don't support projection queries, this is moot.
|
| -
|
| - if eqProperties.Get([]byte("__key__")) != nil {
|
| - ret.order = []queryOrder{}
|
| - }
|
| -
|
| - newOrders = []queryOrder{}
|
| - for _, o := range ret.order {
|
| - if o.field == "__key__" {
|
| - newOrders = append(newOrders, o)
|
| - break
|
| - }
|
| - newOrders = append(newOrders, o)
|
| - }
|
| - ret.order = newOrders
|
| -
|
| - return
|
| -}
|
| -
|
| -func (q *queryImpl) checkCorrectness(ns string, isTxn bool) (ret *queryImpl) {
|
| - // ported from GAE SDK datastore_stub_util.py;CheckQuery()
|
| - ret = q.clone()
|
| -
|
| - if ns != ret.ns {
|
| - ret.err = errors.New(
|
| - "gae/memory: Namespace mismatched. Query and Datastore don't agree " +
|
| - "on the current namespace")
|
| - return
|
| - }
|
| -
|
| - if ret.err != nil {
|
| - return
|
| - }
|
| -
|
| - // if projection && keys_only:
|
| - // "projection and keys_only cannot both be set"
|
| -
|
| - // if projection props match /^__.*__$/:
|
| - // "projections are not supported for the property: %(prop)s"
|
| -
|
| - if isTxn && ret.ancestor == nil {
|
| - ret.err = errors.New(
|
| - "gae/memory: Only ancestor queries are allowed inside transactions")
|
| - return
|
| - }
|
| -
|
| - numComponents := len(ret.filter) + len(ret.order)
|
| - if ret.ancestor != nil {
|
| - numComponents++
|
| - }
|
| - if numComponents > 100 {
|
| - ret.err = errors.New(
|
| - "gae/memory: query is too large. may not have more than " +
|
| - "100 filters + sort orders ancestor total")
|
| - }
|
| -
|
| - // if ret.ancestor.appid() != current appid
|
| - // "query app is x but ancestor app is x"
|
| - // if ret.ancestor.namespace() != current namespace
|
| - // "query namespace is x but ancestor namespace is x"
|
| -
|
| - // if not all(g in orders for g in group_by)
|
| - // "items in the group by clause must be specified first in the ordering"
|
| -
|
| - ineqPropName := ""
|
| - for _, f := range ret.filter {
|
| - if f.field == "__key__" {
|
| - k, ok := f.value.(gae.DSKey)
|
| - if !ok {
|
| - ret.err = errors.New(
|
| - "gae/memory: __key__ filter value must be a Key")
|
| - return
|
| - }
|
| - if !helper.DSKeyValid(k, ret.ns, false) {
|
| - // See the comment in queryImpl.Ancestor; basically this check
|
| - // never happens in the real env because the SDK silently swallows
|
| - // this condition :/
|
| - ret.err = gae.ErrDSInvalidKey
|
| - return
|
| - }
|
| - // __key__ filter app is X but query app is X
|
| - // __key__ filter namespace is X but query namespace is X
|
| - }
|
| - // if f.op == qEqual and f.field in ret.project_fields
|
| - // "cannot use projection on a proprety with an equality filter"
|
| -
|
| - if f.op.isINEQOp() {
|
| - if ineqPropName == "" {
|
| - ineqPropName = f.field
|
| - } else if f.field != ineqPropName {
|
| - ret.err = fmt.Errorf(
|
| - "gae/memory: Only one inequality filter per query is supported. "+
|
| - "Encountered both %s and %s", ineqPropName, f.field)
|
| - return
|
| - }
|
| - }
|
| - }
|
| -
|
| - // if ineqPropName != "" && len(group_by) > 0 && len(orders) ==0
|
| - // "Inequality filter on X must also be a group by property "+
|
| - // "when group by properties are set."
|
| -
|
| - if ineqPropName != "" && len(ret.order) != 0 {
|
| - if ret.order[0].field != ineqPropName {
|
| - ret.err = fmt.Errorf(
|
| - "gae/memory: The first sort property must be the same as the property "+
|
| - "to which the inequality filter is applied. In your query "+
|
| - "the first sort property is %s but the inequality filter "+
|
| - "is on %s", ret.order[0].field, ineqPropName)
|
| - return
|
| - }
|
| - }
|
| -
|
| - if ret.kind == "" {
|
| - for _, f := range ret.filter {
|
| - if f.field != "__key__" {
|
| - ret.err = errors.New(
|
| - "gae/memory: kind is required for non-__key__ filters")
|
| - return
|
| - }
|
| - }
|
| - for _, o := range ret.order {
|
| - if o.field != "__key__" || o.direction != qASC {
|
| - ret.err = errors.New(
|
| - "gae/memory: kind is required for all orders except __key__ ascending")
|
| - return
|
| - }
|
| - }
|
| - }
|
| - return
|
| -}
|
| -
|
| -func (q *queryImpl) calculateIndex() *qIndex {
|
| - // as a nod to simplicity in this code, we'll require that a single index
|
| - // is able to service the entire query. E.g. no zigzag merge joins or
|
| - // multiqueries. This will mean that the user will need to rely on
|
| - // dev_appserver to tell them what indicies they need for real, and for thier
|
| - // tests they'll need to specify the missing composite indices manually.
|
| - //
|
| - // This COULD lead to an exploding indicies problem, but we can fix that when
|
| - // we get to it.
|
| -
|
| - //sortOrders := []qSortBy{}
|
| -
|
| - return nil
|
| -}
|
| -
|
| -func (q *queryImpl) clone() *queryImpl {
|
| - ret := *q
|
| - ret.filter = append([]queryFilter(nil), q.filter...)
|
| - ret.order = append([]queryOrder(nil), q.order...)
|
| - ret.project = append([]string(nil), q.project...)
|
| - return &ret
|
| -}
|
| -
|
| -func (q *queryImpl) Ancestor(k gae.DSKey) gae.DSQuery {
|
| - q = q.clone()
|
| - q.ancestor = k
|
| - if k == nil {
|
| - // SDK has an explicit nil-check
|
| - q.err = errors.New("datastore: nil query ancestor")
|
| - } else if !helper.DSKeyValid(k, q.ns, false) {
|
| - // technically the SDK implementation does a Weird Thing (tm) if both the
|
| - // stringID and intID are set on a key; it only serializes the stringID in
|
| - // the proto. This means that if you set the Ancestor to an invalid key,
|
| - // you'll never actually hear about it. Instead of doing that insanity, we
|
| - // just swap to an error here.
|
| - q.err = gae.ErrDSInvalidKey
|
| - }
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) Distinct() gae.DSQuery {
|
| - q = q.clone()
|
| - q.distinct = true
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) Filter(fStr string, val interface{}) gae.DSQuery {
|
| - q = q.clone()
|
| - f, err := parseFilter(fStr, val)
|
| - if err != nil {
|
| - q.err = err
|
| - return q
|
| - }
|
| - q.filter = append(q.filter, f)
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) Order(field string) gae.DSQuery {
|
| - q = q.clone()
|
| - field = strings.TrimSpace(field)
|
| - o := queryOrder{field, qASC}
|
| - if strings.HasPrefix(field, "-") {
|
| - o.direction = qDEC
|
| - o.field = strings.TrimSpace(field[1:])
|
| - } else if strings.HasPrefix(field, "+") {
|
| - q.err = fmt.Errorf("datastore: invalid order: %q", field)
|
| - return q
|
| - }
|
| - if len(o.field) == 0 {
|
| - q.err = errors.New("datastore: empty order")
|
| - return q
|
| - }
|
| - q.order = append(q.order, o)
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) Project(fieldName ...string) gae.DSQuery {
|
| - q = q.clone()
|
| - q.project = append(q.project, fieldName...)
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) KeysOnly() gae.DSQuery {
|
| - q = q.clone()
|
| - q.keysOnly = true
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) Limit(limit int) gae.DSQuery {
|
| - 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
|
| -}
|
| -
|
| -func (q *queryImpl) Offset(offset int) gae.DSQuery {
|
| - 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
|
| -}
|
| -
|
| -func (q *queryImpl) Start(c gae.DSCursor) gae.DSQuery {
|
| - q = q.clone()
|
| - curs := c.(queryCursor)
|
| - if !curs.Valid() {
|
| - q.err = errors.New("datastore: invalid cursor")
|
| - return q
|
| - }
|
| - q.start = curs
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) End(c gae.DSCursor) gae.DSQuery {
|
| - q = q.clone()
|
| - curs := c.(queryCursor)
|
| - if !curs.Valid() {
|
| - q.err = errors.New("datastore: invalid cursor")
|
| - return q
|
| - }
|
| - q.end = curs
|
| - return q
|
| -}
|
| -
|
| -func (q *queryImpl) EventualConsistency() gae.DSQuery {
|
| - q = q.clone()
|
| - q.eventualConsistency = true
|
| - return q
|
| -}
|
|
|