| Index: service/datastore/properties.go
|
| diff --git a/service/datastore/properties.go b/service/datastore/properties.go
|
| index 2808c3804702d400198792664ac025441fe2a3da..7206c98356fbb87779dfceee8b5be83ecbc8d87e 100644
|
| --- a/service/datastore/properties.go
|
| +++ b/service/datastore/properties.go
|
| @@ -336,6 +336,16 @@ func UpconvertUnderlyingType(o interface{}) interface{} {
|
| return o
|
| }
|
|
|
| +func (Property) isAPropertyData() {}
|
| +
|
| +func (p Property) estimateSize() int64 { return p.EstimateSize() }
|
| +
|
| +// Slice implements the PropertyData interface.
|
| +func (p Property) Slice() PropertySlice { return PropertySlice{p} }
|
| +
|
| +// Clone implements the PropertyData interface.
|
| +func (p Property) Clone() PropertyData { return p }
|
| +
|
| func (p Property) String() string {
|
| switch p.propType {
|
| case PTString, PTBlobKey:
|
| @@ -628,6 +638,33 @@ func (p *Property) Compare(other *Property) int {
|
| }
|
| }
|
|
|
| +// EstimateSize estimates the amount of space that this Property would consume
|
| +// if it were committed as part of an entity in the real production datastore.
|
| +//
|
| +// It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
|
| +// as a guide for these values.
|
| +func (p *Property) EstimateSize() int64 {
|
| + switch p.Type() {
|
| + case PTNull:
|
| + return 1
|
| + case PTBool:
|
| + return 1 + 4
|
| + case PTInt, PTTime, PTFloat:
|
| + return 1 + 8
|
| + case PTGeoPoint:
|
| + return 1 + (8 * 2)
|
| + case PTString:
|
| + return 1 + int64(len(p.Value().(string)))
|
| + case PTBlobKey:
|
| + return 1 + int64(len(p.Value().(blobstore.Key)))
|
| + case PTBytes:
|
| + return 1 + int64(len(p.Value().([]byte)))
|
| + case PTKey:
|
| + return 1 + p.Value().(*Key).EstimateSize()
|
| + }
|
| + panic(fmt.Errorf("Unknown property type: %s", p.Type().String()))
|
| +}
|
| +
|
| // GQL returns a correctly formatted Cloud Datastore GQL literal which
|
| // is valid for a comparison value in the `WHERE` clause.
|
| //
|
| @@ -672,39 +709,36 @@ func (p *Property) GQL() string {
|
| }
|
|
|
| // PropertySlice is a slice of Properties. It implements sort.Interface.
|
| +//
|
| +// PropertySlice holds multiple Properties. Writing a PropertySlice to datastore
|
| +// implicitly marks the property as "multiple", even if it only has one element.
|
| 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]) }
|
| +func (PropertySlice) isAPropertyData() {}
|
|
|
| -// EstimateSize estimates the amount of space that this Property would consume
|
| -// if it were committed as part of an entity in the real production datastore.
|
| -//
|
| -// It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
|
| -// as a guide for these values.
|
| -func (p *Property) EstimateSize() int64 {
|
| - switch p.Type() {
|
| - case PTNull:
|
| - return 1
|
| - case PTBool:
|
| - return 1 + 4
|
| - case PTInt, PTTime, PTFloat:
|
| - return 1 + 8
|
| - case PTGeoPoint:
|
| - return 1 + (8 * 2)
|
| - case PTString:
|
| - return 1 + int64(len(p.Value().(string)))
|
| - case PTBlobKey:
|
| - return 1 + int64(len(p.Value().(blobstore.Key)))
|
| - case PTBytes:
|
| - return 1 + int64(len(p.Value().([]byte)))
|
| - case PTKey:
|
| - return 1 + p.Value().(*Key).EstimateSize()
|
| +// Clone implements the PropertyData interface.
|
| +func (s PropertySlice) Clone() PropertyData { return s.Slice() }
|
| +
|
| +// Slice implements the PropertyData interface.
|
| +func (s PropertySlice) Slice() PropertySlice {
|
| + if len(s) == 0 {
|
| + return nil
|
| }
|
| - panic(fmt.Errorf("Unknown property type: %s", p.Type().String()))
|
| + return append(make(PropertySlice, 0, len(s)), s...)
|
| }
|
|
|
| +func (s PropertySlice) estimateSize() (v int64) {
|
| + for _, prop := range s {
|
| + // Use the public one so we don't have to copy.
|
| + v += prop.estimateSize()
|
| + }
|
| + return
|
| +}
|
| +
|
| +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 {
|
| @@ -785,6 +819,27 @@ type MetaGetterSetter interface {
|
| SetMeta(key string, val interface{}) bool
|
| }
|
|
|
| +// PropertyData is an interface implemented by Property and PropertySlice to
|
| +// identify themselves as valid PropertyMap values.
|
| +type PropertyData interface {
|
| + // isAPropertyData is a tag that forces PropertyData implementations to only
|
| + // be supplied by this package.
|
| + isAPropertyData()
|
| +
|
| + // Slice returns a PropertySlice representation of this PropertyData.
|
| + //
|
| + // The returned PropertySlice is a clone of the original data. Consequently,
|
| + // Consequently, Property-modifying methods such as SetValue should NOT be
|
| + // called on the results.
|
| + Slice() PropertySlice
|
| +
|
| + // estimateSize estimates the aggregate size of the property data.
|
| + estimateSize() int64
|
| +
|
| + // Clone creates a duplicate copy of this PropertyData.
|
| + Clone() PropertyData
|
| +}
|
| +
|
| // 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
|
| @@ -808,14 +863,14 @@ type MetaGetterSetter interface {
|
| //
|
| // 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
|
| +type PropertyMap map[string]PropertyData
|
|
|
| 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...)
|
| + pm[k] = v.Clone()
|
| }
|
| return nil
|
| }
|
| @@ -829,7 +884,7 @@ func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) {
|
| ret := make(PropertyMap, len(pm))
|
| for k, v := range pm {
|
| if withMeta || !isMetaKey(k) {
|
| - ret[k] = append(ret[k], v...)
|
| + ret[k] = v.Clone()
|
| }
|
| }
|
| return ret, nil
|
| @@ -838,11 +893,11 @@ func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) {
|
| // GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value
|
| // associated with the metadata key.
|
| func (pm PropertyMap) GetMeta(key string) (interface{}, bool) {
|
| - v, ok := pm["$"+key]
|
| - if !ok || len(v) == 0 {
|
| - return nil, false
|
| + pslice := pm.Slice("$" + key)
|
| + if len(pslice) > 0 {
|
| + return pslice[0].Value(), true
|
| }
|
| - return v[0].Value(), true
|
| + return nil, false
|
| }
|
|
|
| // GetAllMeta implements PropertyLoadSaver.GetAllMeta.
|
| @@ -850,9 +905,7 @@ func (pm PropertyMap) GetAllMeta() PropertyMap {
|
| ret := make(PropertyMap, 8)
|
| for k, v := range pm {
|
| if isMetaKey(k) {
|
| - newV := make([]Property, len(v))
|
| - copy(newV, v)
|
| - ret[k] = newV
|
| + ret[k] = v.Clone()
|
| }
|
| }
|
| return ret
|
| @@ -865,7 +918,7 @@ func (pm PropertyMap) SetMeta(key string, val interface{}) bool {
|
| if err := prop.SetValue(val, NoIndex); err != nil {
|
| return false
|
| }
|
| - pm["$"+key] = []Property{prop}
|
| + pm["$"+key] = prop
|
| return true
|
| }
|
|
|
| @@ -874,6 +927,18 @@ func (pm PropertyMap) Problem() error {
|
| return nil
|
| }
|
|
|
| +// Slice returns a PropertySlice for the given key
|
| +//
|
| +// If the value associated with that key is nil, an empty slice will be
|
| +// returned. If the value is single Property, a slice of size 1 with that
|
| +// Property in it will be returned.
|
| +func (pm PropertyMap) Slice(key string) PropertySlice {
|
| + if pdata := pm[key]; pdata != nil {
|
| + return pdata.Slice()
|
| + }
|
| + return nil
|
| +}
|
| +
|
| // EstimateSize estimates the size that it would take to encode this PropertyMap
|
| // in the production Appengine datastore. The calculation excludes metadata
|
| // fields in the map.
|
| @@ -885,9 +950,7 @@ func (pm PropertyMap) EstimateSize() int64 {
|
| for k, vals := range pm {
|
| if !isMetaKey(k) {
|
| ret += int64(len(k))
|
| - for i := range vals {
|
| - ret += vals[i].EstimateSize()
|
| - }
|
| + ret += vals.estimateSize()
|
| }
|
| }
|
| return ret
|
|
|