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

Unified Diff: service/rawdatastore/properties.go

Issue 1259593005: Add 'user friendly' datastore API. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: 100% coverage of new code Created 5 years, 5 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
Index: service/rawdatastore/properties.go
diff --git a/service/rawdatastore/properties.go b/service/rawdatastore/properties.go
deleted file mode 100644
index 388afba8506d41c874c6e249bfd6c6c301a7f63f..0000000000000000000000000000000000000000
--- a/service/rawdatastore/properties.go
+++ /dev/null
@@ -1,451 +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 rawdatastore
-
-import (
- "errors"
- "fmt"
- "math"
- "reflect"
- "time"
-
- "github.com/luci/gae/service/blobstore"
-)
-
-var (
- minTime = time.Unix(int64(math.MinInt64)/1e6, (int64(math.MinInt64)%1e6)*1e3)
- maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3)
-)
-
-// IndexSetting indicates whether or not a Property should be indexed by the
-// datastore.
-type IndexSetting bool
-
-// ShouldIndex is the default, which is why it must assume the zero value,
-// even though it's werid :(.
-const (
- ShouldIndex IndexSetting = false
- NoIndex IndexSetting = true
-)
-
-func (i IndexSetting) String() string {
- if i {
- return "NoIndex"
- }
- 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 RawDatastore. Its ToProperty will be called on save, and
-// it's FromProperty will be called on load (from datastore). The method may
-// do arbitrary computation, and if it encounters an error, may return it. This
-// error will be a fatal error (as defined by PropertyLoadSaver) for the
-// struct conversion.
-//
-// Example:
-// type Complex complex
-// func (c *Complex) ToProperty() (ret Property, err error) {
-// // something like:
-// err = ret.SetValue(fmt.Sprint(*c), true)
-// return
-// }
-// func (c *Complex) FromProperty(p Property) (err error) {
-// ... load *c from p ...
-// }
-//
-// type MyStruct struct {
-// Complexity []Complex // acts like []complex, but can be serialized to DS
-// }
-type PropertyConverter interface {
- // TODO(riannucci): Allow a convertable to return multiple values. This is
- // eminently doable (as long as the single-slice restriction is kept). It
- // could also cut down on the amount of reflection necessary when resolving
- // a path in a struct (in the struct loading routine in helper).
-
- ToProperty() (Property, error)
- FromProperty(Property) error
-}
-
-// PropertyType is a single-byte representation of the type of data contained
-// in a Property. The specific values of this type information are chosen so
-// that the types sort according to the order of types as sorted by the
-// datastore.
-type PropertyType byte
-
-// These constants are in the order described by
-// https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type_ordering
-// with a slight divergence for the Int/Time split.
-// NOTE: this enum can only occupy 7 bits, because we use the high bit to encode
-// indexed/non-indexed. See typData.WriteBinary.
-const (
- PTNull PropertyType = iota
- PTInt
-
- // PTTime is a slight divergence from the way that datastore natively stores
- // time. In datastore, times and integers actually sort together
- // (apparently?). This is probably insane, and I don't want to add the
- // complexity of field 'meaning' as a sparate concept from the field's 'type'
- // (which is what datastore seems to do, judging from the protobufs). So if
- // you're here because you implemented an app which relies on time.Time and
- // int64 sorting together, then this is why your app acts differently in
- // production. My advice is to NOT DO THAT. If you really want this (and you
- // probably don't), you should take care of the time.Time <-> int64 conversion
- // in your app and just use a property type of int64 (consider using
- // PropertyConverter).
- PTTime
-
- // PTBoolFalse and True are also a slight divergence, but not a semantic
- // one. IIUC, in datastore 'bool' is actually the type and the value is either
- // 0 or 1 (taking another byte to store). Since we have plenty of space in
- // this type byte, I just merge the value into the type for booleans. If this
- // becomes problematic, consider changing this to just pvBool, and then
- // encoding a 0 or 1 as a byte in the relevant marshalling routines.
- PTBoolFalse
- PTBoolTrue
-
- PTBytes // []byte or datastore.ByteString
- PTString // string or string noindex
- PTFloat
- PTGeoPoint
- PTKey
- PTBlobKey
-
- PTUnknown
-)
-
-func (t PropertyType) String() string {
- switch t {
- case PTNull:
- return "PTNull"
- case PTInt:
- return "PTInt"
- case PTTime:
- return "PTTime"
- case PTBoolFalse:
- return "PTBoolFalse"
- case PTBoolTrue:
- return "PTBoolTrue"
- case PTBytes:
- return "PTBytes"
- case PTString:
- return "PTString"
- case PTFloat:
- return "PTFloat"
- case PTGeoPoint:
- return "PTGeoPoint"
- case PTKey:
- return "PTKey"
- case PTBlobKey:
- return "PTBlobKey"
- default:
- return fmt.Sprintf("PTUnknown(%02x)", byte(t))
- }
-}
-
-// 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.
-func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) {
- switch x := v.(type) {
- case nil:
- return PTNull, nil
- case int64:
- return PTInt, nil
- case float64:
- return PTFloat, nil
- case bool:
- if x {
- return PTBoolTrue, nil
- }
- return PTBoolFalse, nil
- case []byte, ByteString:
- return PTBytes, nil
- case blobstore.Key:
- return PTBlobKey, nil
- case string:
- return PTString, nil
- case Key:
- // TODO(riannucci): Check key for validity in its own namespace?
- return PTKey, nil
- case time.Time:
- err := error(nil)
- if checkValid && (x.Before(minTime) || x.After(maxTime)) {
- err = errors.New("time value out of range")
- }
- if checkValid && x.Location() != time.UTC {
- err = fmt.Errorf("time value has wrong Location: %s", x.Location())
- }
- return PTTime, err
- case GeoPoint:
- err := error(nil)
- if checkValid && !x.Valid() {
- err = errors.New("invalid GeoPoint value")
- }
- return PTGeoPoint, err
- default:
- return PTUnknown, fmt.Errorf("gae: Property has bad type %T", v)
- }
-}
-
-// UpconvertUnderlyingType takes an object o, and attempts to convert it to
-// its native datastore-compatible type. e.g. int16 will convert to int64, and
-// `type Foo string` will convert to `string`.
-func UpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, reflect.Type) {
- v := reflect.ValueOf(o)
- switch t.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- o = v.Int()
- t = typeOfInt64
- case reflect.Bool:
- o = v.Bool()
- t = typeOfBool
- case reflect.String:
- if t != typeOfBSKey {
- o = v.String()
- t = typeOfString
- }
- case reflect.Float32, reflect.Float64:
- o = v.Float()
- t = typeOfFloat64
- case reflect.Slice:
- if t != typeOfByteString && t.Elem().Kind() == reflect.Uint8 {
- o = v.Bytes()
- t = typeOfByteSlice
- }
- case reflect.Struct:
- if t == typeOfTime {
- // time in a Property can only hold microseconds
- o = v.Interface().(time.Time).Round(time.Microsecond)
- }
- }
- return o, t
-}
-
-// Value returns the current value held by this property. It's guaranteed to
-// be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return
-// an error).
-func (p *Property) Value() interface{} { return p.value }
-
-// IndexSetting says weather or not the datastore should create indicies for
-// this value.
-func (p *Property) IndexSetting() IndexSetting { return p.indexSetting }
-
-// Type is the PT* type of the data contained in Value().
-func (p *Property) Type() PropertyType { return p.propType }
-
-// SetValue sets the Value field of a Property, and ensures that its value
-// conforms to the permissible types. That way, you're guaranteed that if you
-// have a Property, its value is valid.
-//
-// value is the property value. The valid types are:
-// - int64
-// - bool
-// - string
-// - float64
-// - ByteString
-// - Key
-// - time.Time
-// - blobstore.Key
-// - GeoPoint
-// - []byte (up to 1 megabyte in length)
-// 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
-// from []byte); use multiple Properties instead. Also, a Value's type
-// must be explicitly on the list above; it is not sufficient for the
-// underlying type to be on that list. For example, a Value of "type
-// myInt64 int64" is invalid. Smaller-width integers and floats are also
-// invalid. Again, this is more restrictive than the set of valid struct
-// field types.
-//
-// A value may also be the nil interface value; this is equivalent to
-// Python's None but not directly representable by a Go struct. Loading
-// a nil-valued property into a struct will set that field to the zero
-// value.
-func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) {
- t := reflect.Type(nil)
- pt := PTNull
- if value != nil {
- t = reflect.TypeOf(value)
- value, t = UpconvertUnderlyingType(value, t)
- if pt, err = PropertyTypeOf(value, true); err != nil {
- return
- }
- }
- p.propType = pt
- p.value = value
- p.indexSetting = is
- if t == typeOfByteSlice {
- p.indexSetting = NoIndex
- }
- return
-}
-
-// PropertyLoadSaver may be implemented by a user type, and RawDatastore will
-// use this interface to serialize the type instead of trying to automatically
-// create a serialization codec for it with helper.GetPLS.
-type PropertyLoadSaver interface {
- // Load takes the values from the given map and attempts to save them into
- // the underlying object (usually a struct or a PropertyMap). If a fatal
- // error occurs, it's returned via error. If non-fatal conversion errors
- // occur, error will be a MultiError containing one or more ErrFieldMismatch
- // objects.
- Load(PropertyMap) error
-
- // Save returns the current property as a PropertyMap. if withMeta is true,
- // then the PropertyMap contains all the metadata (e.g. '$meta' fields)
- // which was held by this PropertyLoadSaver.
- Save(withMeta bool) (PropertyMap, error)
-
- // GetMeta will get information about the field which has the struct tag in
- // the form of `gae:"$<key>[,<value>]?"`.
- //
- // string and int64 fields will return the <value> in the struct tag,
- // converted to the appropriate type, if the field has the zero value.
- //
- // Example:
- // type MyStruct struct {
- // CoolField int64 `gae:"$id,1"`
- // }
- // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id")
- // // val == 1
- // // err == nil
- //
- // val, err := helper.GetPLS(&MyStruct{10}).GetMeta("id")
- // // val == 10
- // // err == nil
- //
- // Struct fields of type Toggle (which is an Auto/On/Off) allow you to
- // specify a value of 'true' or 'false' for the default value of the struct
- // tag, and GetMeta will return the combined value as a regular boolean true
- // or false value. If a field is Toggle, a <value> MUST be specified.
- //
- // Example:
- // type MyStruct struct {
- // TFlag Toggle `gae:"$flag1,true"` // defaults to true
- // FFlag Toggle `gae:"$flag2,false"` // defaults to false
- // // BadFlag Toggle `gae:"$flag3"` // ILLEGAL
- // }
- GetMeta(key string) (interface{}, error)
-
- // SetMeta allows you to set the current value of the meta-keyed field.
- SetMeta(key string, val interface{}) error
-
- // Problem indicates that this PLS has a fatal problem. Usually this is
- // set when the underlying struct has recursion, invalid field types, nested
- // slices, etc.
- Problem() error
-}
-
-// PropertyMap represents the contents of a datastore entity in a generic way.
-// It maps from property name to a list of property values which correspond to
-// that property name. It is the spiritual successor to PropertyList from the
-// original SDK.
-//
-// PropertyMap may contain "meta" values, which are keyed with a '$' prefix.
-// Technically the datastore allows arbitrary property names, but all of the
-// SDKs go out of their way to try to make all property names valid programming
-// language tokens. Special values must correspond to a single Property...
-// corresponding to 0 is equivalent to unset, and corresponding to >1 is an
-// error. So:
-//
-// {
-// "$id": {MkProperty(1)}, // GetProperty("id") -> 1, nil
-// "$foo": {}, // GetProperty("foo") -> nil, ErrMetaFieldUnset
-// // GetProperty("bar") -> nil, ErrMetaFieldUnset
-// "$meep": {
-// MkProperty("hi"),
-// MkProperty("there")}, // GetProperty("meep") -> nil, error!
-// }
-//
-// Additionally, Save returns a copy of the map with the meta keys omitted (e.g.
-// these keys are not going to be serialized to the datastore).
-type PropertyMap map[string][]Property
-
-var _ PropertyLoadSaver = PropertyMap(nil)
-
-// Load implements PropertyLoadSaver.Load
-func (pm PropertyMap) Load(props PropertyMap) error {
- for k, v := range props {
- pm[k] = append(pm[k], v...)
- }
- return nil
-}
-
-// Save implements PropertyLoadSaver.Save by returning a copy of the
-// current map data.
-func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) {
- if len(pm) == 0 {
- return PropertyMap{}, nil
- }
- ret := make(PropertyMap, len(pm))
- for k, v := range pm {
- if withMeta || len(k) == 0 || k[0] != '$' {
- ret[k] = append(ret[k], v...)
- }
- }
- return ret, nil
-}
-
-// GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value
-// associated with the metadata key. It may return ErrMetaFieldUnset if the
-// key doesn't exist.
-func (pm PropertyMap) GetMeta(key string) (interface{}, error) {
- v, ok := pm["$"+key]
- if !ok || len(v) == 0 {
- return nil, ErrMetaFieldUnset
- }
- if len(v) > 1 {
- return nil, errors.New("gae: too many values for Meta key")
- }
- return v[0].Value(), nil
-}
-
-// SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error
-// if `val` has an invalid type (e.g. not one supported by Property).
-func (pm PropertyMap) SetMeta(key string, val interface{}) error {
- prop := Property{}
- if err := prop.SetValue(val, NoIndex); err != nil {
- return err
- }
- pm["$"+key] = []Property{prop}
- return nil
-}
-
-// Problem implements PropertyLoadSaver.Problem. It ALWAYS returns nil.
-func (pm PropertyMap) Problem() error {
- return nil
-}

Powered by Google App Engine
This is Rietveld 408576698