Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(592)

Unified Diff: service/datastore/properties.go

Issue 1355783002: Refactor keys and queries in datastore service and implementation. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: appease errcheck Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « service/datastore/pls_test.go ('k') | service/datastore/properties_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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)
}
« no previous file with comments | « service/datastore/pls_test.go ('k') | service/datastore/properties_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698