| Index: service/datastore/index.go
|
| diff --git a/service/datastore/index.go b/service/datastore/index.go
|
| index 44cb172bd05fa2c340a958a48598b5a6d1f521bf..4c2af0779146b8fe8d2827f90efcf2efe4e4c914 100644
|
| --- a/service/datastore/index.go
|
| +++ b/service/datastore/index.go
|
| @@ -6,46 +6,78 @@ package datastore
|
|
|
| import (
|
| "bytes"
|
| + "fmt"
|
| + "strings"
|
| )
|
|
|
| -const MaxIndexColumns = 64
|
| -
|
| -type IndexDirection bool
|
| -
|
| -const (
|
| - // ASCENDING is false so that it's the default (zero) value.
|
| - ASCENDING IndexDirection = false
|
| - DESCENDING = true
|
| -)
|
| -
|
| -func (i IndexDirection) String() string {
|
| - if i == ASCENDING {
|
| - return "ASCENDING"
|
| - }
|
| - return "DESCENDING"
|
| +// IndexColumn represents a sort order for a single entity field.
|
| +type IndexColumn struct {
|
| + Property string
|
| + Descending bool
|
| }
|
|
|
| -type IndexColumn struct {
|
| - Property string
|
| - Direction IndexDirection
|
| +// ParseIndexColumn takes a spec in the form of /\s*-?\s*.+\s*/, and
|
| +// returns an IndexColumn. Examples are:
|
| +// `- Field `: IndexColumn{Property: "Field", Descending: true}
|
| +// `Something`: IndexColumn{Property: "Something", Descending: false}
|
| +//
|
| +// `+Field` is invalid. `` is invalid.
|
| +func ParseIndexColumn(spec string) (IndexColumn, error) {
|
| + col := IndexColumn{}
|
| + if strings.HasPrefix(spec, "-") {
|
| + col.Descending = true
|
| + col.Property = strings.TrimSpace(spec[1:])
|
| + } else if strings.HasPrefix(spec, "+") {
|
| + return col, fmt.Errorf("datastore: invalid order: %q", spec)
|
| + } else {
|
| + col.Property = strings.TrimSpace(spec)
|
| + }
|
| + if col.Property == "" {
|
| + return col, fmt.Errorf("datastore: empty order")
|
| + }
|
| + return col, nil
|
| }
|
|
|
| func (i IndexColumn) cmp(o IndexColumn) int {
|
| // sort ascending first
|
| - if i.Direction == ASCENDING && o.Direction == DESCENDING {
|
| + if !i.Descending && o.Descending {
|
| return -1
|
| - } else if i.Direction == DESCENDING && o.Direction == ASCENDING {
|
| + } else if i.Descending && !o.Descending {
|
| return 1
|
| }
|
| return cmpString(i.Property, o.Property)()
|
| }
|
|
|
| +// String returns a human-readable version of this IndexColumn which is
|
| +// compatible with ParseIndexColumn.
|
| +func (i IndexColumn) String() string {
|
| + ret := ""
|
| + if i.Descending {
|
| + ret = "-"
|
| + }
|
| + return ret + i.Property
|
| +}
|
| +
|
| +// GQL returns a correctly formatted Cloud Datastore GQL literal which
|
| +// is valid for the `ORDER BY` clause.
|
| +//
|
| +// The flavor of GQL that this emits is defined here:
|
| +// https://cloud.google.com/datastore/docs/apis/gql/gql_reference
|
| +func (i IndexColumn) GQL() string {
|
| + if i.Descending {
|
| + return gqlQuoteName(i.Property) + " DESC"
|
| + }
|
| + return gqlQuoteName(i.Property)
|
| +}
|
| +
|
| +// IndexDefinition holds the parsed definition of a datastore index definition.
|
| type IndexDefinition struct {
|
| Kind string
|
| Ancestor bool
|
| SortBy []IndexColumn
|
| }
|
|
|
| +// Equal returns true if the two IndexDefinitions are equivalent.
|
| func (id *IndexDefinition) Equal(o *IndexDefinition) bool {
|
| if id.Kind != o.Kind || id.Ancestor != o.Ancestor || len(id.SortBy) != len(o.SortBy) {
|
| return false
|
| @@ -58,7 +90,8 @@ func (id *IndexDefinition) Equal(o *IndexDefinition) bool {
|
| return true
|
| }
|
|
|
| -// NormalizeOrder returns the normalized SortBy value for this IndexDefinition.
|
| +// Normalize returns an IndexDefinition which has a normalized SortBy field.
|
| +//
|
| // This is just appending __key__ if it's not explicitly the last field in this
|
| // IndexDefinition.
|
| func (id *IndexDefinition) Normalize() *IndexDefinition {
|
| @@ -72,6 +105,9 @@ func (id *IndexDefinition) Normalize() *IndexDefinition {
|
| return &ret
|
| }
|
|
|
| +// GetFullSortOrder gets the full sort order for this IndexDefinition,
|
| +// including an extra "__ancestor__" column at the front if this index has
|
| +// Ancestor set to true.
|
| func (id *IndexDefinition) GetFullSortOrder() []IndexColumn {
|
| id = id.Normalize()
|
| if !id.Ancestor {
|
| @@ -82,10 +118,12 @@ func (id *IndexDefinition) GetFullSortOrder() []IndexColumn {
|
| return append(ret, id.SortBy...)
|
| }
|
|
|
| +// PrepForIdxTable normalize and then flips the IndexDefinition.
|
| func (id *IndexDefinition) PrepForIdxTable() *IndexDefinition {
|
| return id.Normalize().Flip()
|
| }
|
|
|
| +// Flip returns an IndexDefinition with its SortBy field in reverse order.
|
| func (id *IndexDefinition) Flip() *IndexDefinition {
|
| ret := *id
|
| ret.SortBy = make([]IndexColumn, 0, len(id.SortBy))
|
| @@ -134,7 +172,8 @@ func cmpString(a, b string) func() int {
|
| }
|
| }
|
|
|
| -func (i *IndexDefinition) Less(o *IndexDefinition) bool {
|
| +// Less returns true iff id is ordered before o.
|
| +func (id *IndexDefinition) Less(o *IndexDefinition) bool {
|
| decide := func(v int) (ret, keepGoing bool) {
|
| if v > 0 {
|
| return false, false
|
| @@ -146,10 +185,10 @@ func (i *IndexDefinition) Less(o *IndexDefinition) bool {
|
| }
|
|
|
| factors := []func() int{
|
| - cmpBool(i.Builtin(), o.Builtin()),
|
| - cmpString(i.Kind, o.Kind),
|
| - cmpBool(i.Ancestor, o.Ancestor),
|
| - cmpInt(len(i.SortBy), len(o.SortBy)),
|
| + cmpBool(id.Builtin(), o.Builtin()),
|
| + cmpString(id.Kind, o.Kind),
|
| + cmpBool(id.Ancestor, o.Ancestor),
|
| + cmpInt(len(id.SortBy), len(o.SortBy)),
|
| }
|
| for _, f := range factors {
|
| ret, keepGoing := decide(f())
|
| @@ -157,8 +196,8 @@ func (i *IndexDefinition) Less(o *IndexDefinition) bool {
|
| return ret
|
| }
|
| }
|
| - for idx := range i.SortBy {
|
| - ret, keepGoing := decide(i.SortBy[idx].cmp(o.SortBy[idx]))
|
| + for idx := range id.SortBy {
|
| + ret, keepGoing := decide(id.SortBy[idx].cmp(o.SortBy[idx]))
|
| if !keepGoing {
|
| return ret
|
| }
|
| @@ -166,15 +205,21 @@ func (i *IndexDefinition) Less(o *IndexDefinition) bool {
|
| return false
|
| }
|
|
|
| -func (i *IndexDefinition) Builtin() bool {
|
| - return !i.Ancestor && len(i.SortBy) <= 1
|
| +// Builtin returns true iff the IndexDefinition is one of the automatic built-in
|
| +// indexes.
|
| +func (id *IndexDefinition) Builtin() bool {
|
| + return !id.Ancestor && len(id.SortBy) <= 1
|
| }
|
|
|
| -func (i *IndexDefinition) Compound() bool {
|
| - if i.Kind == "" || len(i.SortBy) <= 1 {
|
| +// Compound returns true iff this IndexDefinition is a valid compound index
|
| +// definition.
|
| +//
|
| +// NOTE: !Builtin() does not imply Compound().
|
| +func (id *IndexDefinition) Compound() bool {
|
| + if id.Kind == "" || len(id.SortBy) <= 1 {
|
| return false
|
| }
|
| - for _, sb := range i.SortBy {
|
| + for _, sb := range id.SortBy {
|
| if sb.Property == "" || sb.Property == "__ancestor__" {
|
| return false
|
| }
|
| @@ -182,24 +227,38 @@ func (i *IndexDefinition) Compound() bool {
|
| return true
|
| }
|
|
|
| -func (i *IndexDefinition) String() string {
|
| +func (id *IndexDefinition) String() string {
|
| ret := &bytes.Buffer{}
|
| - if i.Builtin() {
|
| - ret.WriteRune('B')
|
| + wr := func(r rune) {
|
| + _, err := ret.WriteRune(r)
|
| + if err != nil {
|
| + panic(err)
|
| + }
|
| + }
|
| +
|
| + ws := func(s string) {
|
| + _, err := ret.WriteString(s)
|
| + if err != nil {
|
| + panic(err)
|
| + }
|
| + }
|
| +
|
| + if id.Builtin() {
|
| + wr('B')
|
| } else {
|
| - ret.WriteRune('C')
|
| + wr('C')
|
| }
|
| - ret.WriteRune(':')
|
| - ret.WriteString(i.Kind)
|
| - if i.Ancestor {
|
| - ret.WriteString("|A")
|
| + wr(':')
|
| + ws(id.Kind)
|
| + if id.Ancestor {
|
| + ws("|A")
|
| }
|
| - for _, sb := range i.SortBy {
|
| - ret.WriteRune('/')
|
| - if sb.Direction == DESCENDING {
|
| - ret.WriteRune('-')
|
| + for _, sb := range id.SortBy {
|
| + wr('/')
|
| + if sb.Descending {
|
| + wr('-')
|
| }
|
| - ret.WriteString(sb.Property)
|
| + ws(sb.Property)
|
| }
|
| return ret.String()
|
| }
|
|
|