| Index: service/datastore/properties.go
|
| diff --git a/service/datastore/properties.go b/service/datastore/properties.go
|
| index ab0dfc1714f9dbb0dc703c6b0584e4dbf65dabf7..770d19b0f2b4c1e86995f6239a0c9afe7256eb12 100644
|
| --- a/service/datastore/properties.go
|
| +++ b/service/datastore/properties.go
|
| @@ -5,6 +5,7 @@
|
| package datastore
|
|
|
| import (
|
| + "encoding/base64"
|
| "errors"
|
| "fmt"
|
| "math"
|
| @@ -39,38 +40,6 @@ func (i IndexSetting) String() string {
|
| return "ShouldIndex"
|
| }
|
|
|
| -// Property is a value plus an indicator of whether the value should be
|
| -// indexed. Name and Multiple are stored in the PropertyMap object.
|
| -type Property struct {
|
| - value interface{}
|
| - indexSetting IndexSetting
|
| - propType PropertyType
|
| -}
|
| -
|
| -// MkProperty makes a new indexed* Property and returns it. If val is an
|
| -// invalid value, this panics (so don't do it). If you want to handle the error
|
| -// normally, use SetValue(..., ShouldIndex) instead.
|
| -//
|
| -// *indexed if val is not an unindexable type like []byte.
|
| -func MkProperty(val interface{}) Property {
|
| - ret := Property{}
|
| - if err := ret.SetValue(val, ShouldIndex); err != nil {
|
| - panic(err)
|
| - }
|
| - return ret
|
| -}
|
| -
|
| -// MkPropertyNI makes a new Property (with noindex set to true), and returns
|
| -// it. If val is an invalid value, this panics (so don't do it). If you want to
|
| -// handle the error normally, use SetValue(..., NoIndex) instead.
|
| -func MkPropertyNI(val interface{}) Property {
|
| - ret := Property{}
|
| - if err := ret.SetValue(val, NoIndex); err != nil {
|
| - panic(err)
|
| - }
|
| - return ret
|
| -}
|
| -
|
| // PropertyConverter may be implemented by the pointer-to a struct field which
|
| // is serialized by the struct PropertyLoadSaver from GetPLS. Its ToProperty
|
| // will be called on save, and it's FromProperty will be called on load (from
|
| @@ -176,7 +145,7 @@ const (
|
| // PTGeoPoint is a Projection-query type.
|
| PTGeoPoint
|
|
|
| - // PTKey represents a Key object.
|
| + // PTKey represents a *Key object.
|
| //
|
| // PTKey is a Projection-query type.
|
| PTKey
|
| @@ -184,6 +153,8 @@ const (
|
| // PTBlobKey represents a blobstore.Key
|
| PTBlobKey
|
|
|
| + // PTUnknown is a placeholder value which should never show up in reality.
|
| + //
|
| // NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK.
|
| PTUnknown
|
| )
|
| @@ -225,6 +196,38 @@ func (t PropertyType) String() string {
|
| }
|
| }
|
|
|
| +// Property is a value plus an indicator of whether the value should be
|
| +// indexed. Name and Multiple are stored in the PropertyMap object.
|
| +type Property struct {
|
| + value interface{}
|
| + indexSetting IndexSetting
|
| + propType PropertyType
|
| +}
|
| +
|
| +// MkProperty makes a new indexed* Property and returns it. If val is an
|
| +// invalid value, this panics (so don't do it). If you want to handle the error
|
| +// normally, use SetValue(..., ShouldIndex) instead.
|
| +//
|
| +// *indexed if val is not an unindexable type like []byte.
|
| +func MkProperty(val interface{}) Property {
|
| + ret := Property{}
|
| + if err := ret.SetValue(val, ShouldIndex); err != nil {
|
| + panic(err)
|
| + }
|
| + return ret
|
| +}
|
| +
|
| +// MkPropertyNI makes a new Property (with noindex set to true), and returns
|
| +// it. If val is an invalid value, this panics (so don't do it). If you want to
|
| +// handle the error normally, use SetValue(..., NoIndex) instead.
|
| +func MkPropertyNI(val interface{}) Property {
|
| + ret := Property{}
|
| + if err := ret.SetValue(val, NoIndex); err != nil {
|
| + panic(err)
|
| + }
|
| + return ret
|
| +}
|
| +
|
| // PropertyTypeOf returns the PT* type of the given Property-compatible
|
| // value v. If checkValid is true, this method will also ensure that time.Time
|
| // and GeoPoint have valid values.
|
| @@ -244,7 +247,7 @@ func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) {
|
| return PTBlobKey, nil
|
| case string:
|
| return PTString, nil
|
| - case Key:
|
| + case *Key:
|
| // TODO(riannucci): Check key for validity in its own namespace?
|
| return PTKey, nil
|
| case time.Time:
|
| @@ -338,7 +341,7 @@ func (p *Property) Type() PropertyType { return p.propType }
|
| // - blobstore.Key
|
| // (only the first 1500 bytes is indexable)
|
| // - float64
|
| -// - Key
|
| +// - *Key
|
| // - GeoPoint
|
| // This set is smaller than the set of valid struct field types that the
|
| // datastore can load and save. A Property Value cannot be a slice (apart
|
| @@ -450,6 +453,159 @@ func (p *Property) Project(to PropertyType) (interface{}, error) {
|
| }
|
| }
|
|
|
| +func cmpVals(a, b interface{}, t PropertyType) int {
|
| + cmpFloat := func(a, b float64) int {
|
| + if a == b {
|
| + return 0
|
| + }
|
| + if a > b {
|
| + return 1
|
| + }
|
| + return -1
|
| + }
|
| +
|
| + switch t {
|
| + case PTNull:
|
| + return 0
|
| +
|
| + case PTBool:
|
| + a, b := a.(bool), b.(bool)
|
| + if a == b {
|
| + return 0
|
| + }
|
| + if a && !b {
|
| + return 1
|
| + }
|
| + return -1
|
| +
|
| + case PTInt:
|
| + a, b := a.(int64), b.(int64)
|
| + if a == b {
|
| + return 0
|
| + }
|
| + if a > b {
|
| + return 1
|
| + }
|
| + return -1
|
| +
|
| + case PTString:
|
| + a, b := a.(string), b.(string)
|
| + if a == b {
|
| + return 0
|
| + }
|
| + if a > b {
|
| + return 1
|
| + }
|
| + return -1
|
| +
|
| + case PTFloat:
|
| + return cmpFloat(a.(float64), b.(float64))
|
| +
|
| + case PTGeoPoint:
|
| + a, b := a.(GeoPoint), b.(GeoPoint)
|
| + cmp := cmpFloat(a.Lat, b.Lat)
|
| + if cmp != 0 {
|
| + return cmp
|
| + }
|
| + return cmpFloat(a.Lng, b.Lng)
|
| +
|
| + case PTKey:
|
| + a, b := a.(*Key), b.(*Key)
|
| + if a.Equal(b) {
|
| + return 0
|
| + }
|
| + if b.Less(a) {
|
| + return 1
|
| + }
|
| + return -1
|
| +
|
| + default:
|
| + panic(fmt.Errorf("uncomparable type: %s", t))
|
| + }
|
| +}
|
| +
|
| +// Less returns true iff p would sort before other.
|
| +//
|
| +// This uses datastore's index rules for sorting (e.g.
|
| +// []byte("hello") == "hello")
|
| +func (p *Property) Less(other *Property) bool {
|
| + if p.indexSetting && !other.indexSetting {
|
| + return true
|
| + } else if !p.indexSetting && other.indexSetting {
|
| + return false
|
| + }
|
| + a, b := p.ForIndex(), other.ForIndex()
|
| + cmp := int(a.propType) - int(b.propType)
|
| + if cmp < 0 {
|
| + return true
|
| + } else if cmp > 0 {
|
| + return false
|
| + }
|
| + return cmpVals(a.value, b.value, a.propType) < 0
|
| +}
|
| +
|
| +// Equal returns true iff p and other have identical index representations.
|
| +//
|
| +// This uses datastore's index rules for sorting (e.g.
|
| +// []byte("hello") == "hello")
|
| +func (p *Property) Equal(other *Property) bool {
|
| + ret := p.indexSetting == other.indexSetting
|
| + if ret {
|
| + a, b := p.ForIndex(), other.ForIndex()
|
| + ret = a.propType == b.propType && cmpVals(a.value, b.value, a.propType) == 0
|
| + }
|
| + return ret
|
| +}
|
| +
|
| +// GQL returns a correctly formatted Cloud Datastore GQL literal which
|
| +// is valid for a comparison value in the `WHERE` clause.
|
| +//
|
| +// The flavor of GQL that this emits is defined here:
|
| +// https://cloud.google.com/datastore/docs/apis/gql/gql_reference
|
| +//
|
| +// NOTE: GeoPoint values are emitted with speculated future syntax. There is
|
| +// currently no syntax for literal GeoPoint values.
|
| +func (p *Property) GQL() string {
|
| + switch p.propType {
|
| + case PTNull:
|
| + return "NULL"
|
| +
|
| + case PTInt, PTFloat, PTBool:
|
| + return fmt.Sprint(p.value)
|
| +
|
| + case PTString:
|
| + return gqlQuoteString(p.value.(string))
|
| +
|
| + case PTBytes:
|
| + return fmt.Sprintf("BLOB(%q)",
|
| + base64.URLEncoding.EncodeToString(p.value.([]byte)))
|
| +
|
| + case PTBlobKey:
|
| + return fmt.Sprintf("BLOBKEY(%s)", gqlQuoteString(
|
| + string(p.value.(blobstore.Key))))
|
| +
|
| + case PTKey:
|
| + return p.value.(*Key).GQL()
|
| +
|
| + case PTTime:
|
| + return fmt.Sprintf("DATETIME(%s)", p.value.(time.Time).Format(time.RFC3339Nano))
|
| +
|
| + case PTGeoPoint:
|
| + // note that cloud SQL doesn't support this yet, but take a good guess at
|
| + // it.
|
| + v := p.value.(GeoPoint)
|
| + return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng)
|
| + }
|
| + panic(fmt.Errorf("bad type: %s", p.propType))
|
| +}
|
| +
|
| +// PropertySlice is a slice of Properties. It implements sort.Interface.
|
| +type PropertySlice []Property
|
| +
|
| +func (s PropertySlice) Len() int { return len(s) }
|
| +func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
| +func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) }
|
| +
|
| // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to
|
| // abstract the meta argument for RawInterface.GetMulti.
|
| type MetaGetter interface {
|
| @@ -460,7 +616,7 @@ type MetaGetter interface {
|
| // int64 - may have default (ascii encoded base-10)
|
| // string - may have default
|
| // Toggle - MUST have default ("true" or "false")
|
| - // Key - NO default allowed
|
| + // *Key - NO default allowed
|
| //
|
| // Struct fields of type Toggle (which is an Auto/On/Off) require you to
|
| // specify a value of 'true' or 'false' for the default value of the struct
|
| @@ -590,6 +746,7 @@ func (pm PropertyMap) GetMeta(key string) (interface{}, error) {
|
| return v[0].Value(), nil
|
| }
|
|
|
| +// GetMetaDefault is the implementation of PropertyLoadSaver.GetMetaDefault.
|
| func (pm PropertyMap) GetMetaDefault(key string, dflt interface{}) interface{} {
|
| return GetMetaDefaultImpl(pm.GetMeta, key, dflt)
|
| }
|
|
|