Index: go/src/infra/gae/libs/gae/properties.go |
diff --git a/go/src/infra/gae/libs/gae/properties.go b/go/src/infra/gae/libs/gae/properties.go |
index c7aa9e063e9c992a7b0eeee48186ff171199ac3d..ccf1167de5b62fc5369e30dc09b0fab56418cdeb 100644 |
--- a/go/src/infra/gae/libs/gae/properties.go |
+++ b/go/src/infra/gae/libs/gae/properties.go |
@@ -13,10 +13,10 @@ import ( |
) |
var ( |
- // ErrDSSpecialFieldUnset is returned from DSStructPLS.{Get,Set}Special |
- // implementations when the specified special key isn't set on the struct at |
+ // ErrDSMetaFieldUnset is returned from DSPropertyLoadSaver.{Get,Set}Meta |
+ // implementations when the specified meta key isn't set on the struct at |
// all. |
- ErrDSSpecialFieldUnset = fmt.Errorf("gae: special field unset") |
+ ErrDSMetaFieldUnset = fmt.Errorf("gae: meta field unset") |
) |
var ( |
@@ -37,12 +37,52 @@ var ( |
maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3) |
) |
+type IndexSetting bool |
+ |
+const ( |
+ // ShouldIndex is the default, which is why it must assume the zero value, |
+ // even though it's werid :(. |
+ ShouldIndex IndexSetting = false |
+ NoIndex IndexSetting = true |
+) |
+ |
+func (i IndexSetting) String() string { |
+ if i { |
+ return "NoIndex" |
+ } |
+ return "ShouldIndex" |
+} |
+ |
// DSProperty is a value plus an indicator of whether the value should be |
// indexed. Name and Multiple are stored in the DSPropertyMap object. |
type DSProperty struct { |
- value interface{} |
- noIndex bool |
- propType DSPropertyType |
+ value interface{} |
+ indexSetting IndexSetting |
+ propType DSPropertyType |
+} |
+ |
+// MkDSProperty makes a new indexed* DSProperty 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 MkDSProperty(val interface{}) DSProperty { |
+ ret := DSProperty{} |
+ if err := ret.SetValue(val, ShouldIndex); err != nil { |
+ panic(err) |
+ } |
+ return ret |
+} |
+ |
+// MkDSPropertyNI makes a new DSProperty (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 MkDSPropertyNI(val interface{}) DSProperty { |
+ ret := DSProperty{} |
+ if err := ret.SetValue(val, NoIndex); err != nil { |
+ panic(err) |
+ } |
+ return ret |
} |
// DSPropertyConverter may be implemented by the pointer-to a struct field which |
@@ -236,9 +276,9 @@ func DSUpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, refl |
// an error). |
func (p DSProperty) Value() interface{} { return p.value } |
-// NoIndex says weather or not the datastore should create indicies for this |
-// value. |
-func (p DSProperty) NoIndex() bool { return p.noIndex } |
+// IndexSetting says weather or not the datastore should create indicies for |
+// this value. |
+func (p DSProperty) IndexSetting() IndexSetting { return p.indexSetting } |
// Type is the DSPT* type of the data contained in Value(). |
func (p DSProperty) Type() DSPropertyType { return p.propType } |
@@ -271,10 +311,7 @@ func (p DSProperty) Type() DSPropertyType { return p.propType } |
// 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. |
-// |
-// noIndex == false will attempt to set NoIndex to false as well. However, if |
-// value is an unindexable type, noIndex will be coerced to true automatically. |
-func (p *DSProperty) SetValue(value interface{}, noIndex bool) (err error) { |
+func (p *DSProperty) SetValue(value interface{}, is IndexSetting) (err error) { |
t := reflect.Type(nil) |
pt := DSPTNull |
if value != nil { |
@@ -286,40 +323,52 @@ func (p *DSProperty) SetValue(value interface{}, noIndex bool) (err error) { |
} |
p.propType = pt |
p.value = value |
- p.noIndex = noIndex || t == typeOfByteSlice |
+ p.indexSetting = is |
+ if t == typeOfByteSlice { |
+ p.indexSetting = NoIndex |
+ } |
return |
} |
// DSPropertyLoadSaver 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.GetStructPLS. |
+// create a serialization codec for it with helper.GetPLS. |
type DSPropertyLoadSaver interface { |
- Load(DSPropertyMap) (convFailures []string, fatal error) |
- Save() (DSPropertyMap, error) |
-} |
+ // Load takes the values from the given map and attempts to save them into |
+ // the underlying object (usually a struct or a DSPropertyMap). 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 ErrDSFieldMismatch |
+ // objects. |
+ Load(DSPropertyMap) error |
-// DSStructPLS is a DSPropertyLoadSaver, but with some bonus features which only |
-// apply to user structs (instead of raw DSPropertyMap's). |
-type DSStructPLS interface { |
- DSPropertyLoadSaver |
+ // Save returns the current property as a DSPropertyMap. if withMeta is true, |
+ // then the DSPropertyMap contains all the metadata (e.g. '$meta' fields) |
+ // which was held by this DSPropertyLoadSaver. |
+ Save(withMeta bool) (DSPropertyMap, error) |
- // GetSpecial will get information about the struct field which has the |
- // struct tag in the form of `gae:"$<key>"`. |
+ // 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 int `gae:"$id,cool"` |
+ // CoolField int64 `gae:"$id,1"` |
// } |
- // val, current, err := helper.GetStructPLS(&MyStruct{10}).GetSpecial("id") |
- // // val == "cool" |
- // // current == 10 |
+ // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") |
+ // // val == 1 |
+ // // err == nil |
+ // |
+ // val, err := helper.GetPLS(&MyStruct{10}).GetMeta("id") |
+ // // val == 10 |
// // err == nil |
- GetSpecial(key string) (val string, current interface{}, err error) |
+ GetMeta(key string) (interface{}, error) |
- // SetSpecial allows you to set the current value of the special-keyed field. |
- SetSpecial(key string, val 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 StructPLS has a fatal problem. Usually this is |
+ // 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 |
@@ -329,28 +378,72 @@ type DSStructPLS interface { |
// 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. |
+// |
+// DSPropertyMap 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 DSProperty... |
+// corresponding to 0 is equivalent to unset, and corresponding to >1 is an |
+// error. So: |
+// |
+// { |
+// "$id": {MkDSProperty(1)}, // GetProperty("id") -> 1, nil |
+// "$foo": {}, // GetProperty("foo") -> nil, ErrDSMetaFieldUnset |
+// // GetProperty("bar") -> nil, ErrDSMetaFieldUnset |
+// "$meep": { |
+// MkDSProperty("hi"), |
+// MkDSProperty("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 DSPropertyMap map[string][]DSProperty |
-var _ DSPropertyLoadSaver = (*DSPropertyMap)(nil) |
+var _ DSPropertyLoadSaver = DSPropertyMap(nil) |
// Load implements DSPropertyLoadSaver.Load |
-func (pm *DSPropertyMap) Load(props DSPropertyMap) (convErr []string, err error) { |
- if pm == nil { |
- return nil, errors.New("gae: nil DSPropertyMap") |
+func (pm DSPropertyMap) Load(props DSPropertyMap) error { |
+ for k, v := range props { |
+ pm[k] = append(pm[k], v...) |
+ } |
+ return nil |
+} |
+ |
+// Save implements DSPropertyLoadSaver.Save by returning a copy of the |
+// current map data. |
+func (pm DSPropertyMap) Save(withMeta bool) (DSPropertyMap, error) { |
+ if len(pm) == 0 { |
+ return DSPropertyMap{}, nil |
} |
- if *pm == nil { |
- *pm = make(DSPropertyMap, len(props)) |
+ ret := make(DSPropertyMap, len(pm)) |
+ for k, v := range pm { |
+ if withMeta || len(k) == 0 || k[0] != '$' { |
+ ret[k] = append(ret[k], v...) |
+ } |
} |
- for k, v := range props { |
- (*pm)[k] = append((*pm)[k], v...) |
+ return ret, nil |
+} |
+ |
+func (pm DSPropertyMap) GetMeta(key string) (interface{}, error) { |
+ v, ok := pm["$"+key] |
+ if !ok || len(v) == 0 { |
+ return nil, ErrDSMetaFieldUnset |
+ } |
+ if len(v) > 1 { |
+ return nil, errors.New("gae: too many values for Meta key") |
} |
- return nil, nil |
+ return v[0].Value(), nil |
} |
-// Save implements DSPropertyLoadSaver.Save |
-func (pm *DSPropertyMap) Save() (DSPropertyMap, error) { |
- if pm == nil { |
- return nil, errors.New("gae: nil DSPropertyMap") |
+func (pm DSPropertyMap) SetMeta(key string, val interface{}) error { |
+ prop := DSProperty{} |
+ if err := prop.SetValue(val, NoIndex); err != nil { |
+ return err |
} |
- return *pm, nil |
+ pm["$"+key] = []DSProperty{prop} |
+ return nil |
+} |
+ |
+func (pm DSPropertyMap) Problem() error { |
+ return nil |
} |