Chromium Code Reviews| Index: service/datastore/serialize/serialize.go |
| diff --git a/service/datastore/serialize/serialize.go b/service/datastore/serialize/serialize.go |
| index 736010820f222499212728ca30154029a905cc4f..fe8c7502f0cdc2c3adfae4b25211984a37f0174e 100644 |
| --- a/service/datastore/serialize/serialize.go |
| +++ b/service/datastore/serialize/serialize.go |
| @@ -189,16 +189,26 @@ func ReadGeoPoint(buf Buffer) (gp ds.GeoPoint, err error) { |
| return |
| } |
| -// WriteTime writes a time.Time in a byte-sortable way. |
| +// timeToInt converts a time value to a datastore-appropraite integer value. |
| // |
| // This method truncates the time to microseconds and drops the timezone, |
| // because that's the (undocumented) way that the appengine SDK does it. |
| +func timeToInt(t time.Time) int64 { |
| + t = ds.RoundTime(t) |
| + return t.Unix()*1e6 + int64(t.Nanosecond()/1e3) |
| +} |
| + |
| +// WriteTime writes a time to the buffer. |
| +// |
| +// The supplied time is rounded via datastore.RoundTime and written as a |
| +// microseconds-since-epoch integer to comform to datastore storage standards. |
| func WriteTime(buf Buffer, t time.Time) error { |
| name, off := t.Zone() |
| if name != "UTC" || off != 0 { |
| panic(fmt.Errorf("helper: UTC OR DEATH: %s", t)) |
| } |
| - _, err := cmpbin.WriteInt(buf, t.Unix()*1e6+int64(t.Nanosecond()/1e3)) |
| + |
| + _, err := cmpbin.WriteInt(buf, timeToInt(t)) |
| return err |
| } |
| @@ -208,6 +218,7 @@ func ReadTime(buf Buffer) (time.Time, error) { |
| if err != nil { |
| return time.Time{}, err |
| } |
| + |
| t := time.Unix(v/1e6, (v%1e6)*1e3) |
| if t.IsZero() { |
| return time.Time{}, nil |
| @@ -225,35 +236,70 @@ func WriteProperty(buf Buffer, context KeyContext, p ds.Property) (err error) { |
| typb |= 0x80 |
| } |
| panicIf(buf.WriteByte(typb)) |
| - switch p.Type() { |
| + |
| + err = writeRawProperty(buf, context, p.Type(), p.Value()) |
| + return |
| +} |
| + |
| +func writeRawProperty(buf Buffer, context KeyContext, t ds.PropertyType, val interface{}) (err error) { |
| + t, val = RawType(t, val) |
| + switch t { |
| case ds.PTNull: |
| case ds.PTBool: |
| - b := p.Value().(bool) |
| + b := val.(bool) |
| if b { |
| err = buf.WriteByte(1) |
| } else { |
| err = buf.WriteByte(0) |
| } |
| case ds.PTInt: |
| - _, err = cmpbin.WriteInt(buf, p.Value().(int64)) |
| + _, err = cmpbin.WriteInt(buf, val.(int64)) |
| case ds.PTFloat: |
| - _, err = cmpbin.WriteFloat64(buf, p.Value().(float64)) |
| + _, err = cmpbin.WriteFloat64(buf, val.(float64)) |
| case ds.PTString: |
| - _, err = cmpbin.WriteString(buf, p.Value().(string)) |
| + _, err = cmpbin.WriteString(buf, val.(string)) |
| case ds.PTBytes: |
| - _, err = cmpbin.WriteBytes(buf, p.Value().([]byte)) |
| - case ds.PTTime: |
| - err = WriteTime(buf, p.Value().(time.Time)) |
| + _, err = cmpbin.WriteBytes(buf, val.([]byte)) |
| case ds.PTGeoPoint: |
| - err = WriteGeoPoint(buf, p.Value().(ds.GeoPoint)) |
| + err = WriteGeoPoint(buf, val.(ds.GeoPoint)) |
| case ds.PTKey: |
| - err = WriteKey(buf, context, p.Value().(*ds.Key)) |
| - case ds.PTBlobKey: |
| - _, err = cmpbin.WriteString(buf, string(p.Value().(blobstore.Key))) |
| + err = WriteKey(buf, context, val.(*ds.Key)) |
| + |
| + default: |
| + err = fmt.Errorf("unsupported type: %v", t) |
| } |
| return |
| } |
| +// RawProperty converts a decomposed Property into a Property that matches its |
| +// primitive datastore storage format. |
| +// |
| +// See RawType for more information. |
| +func RawProperty(p ds.Property) ds.Property { |
| + _, v := RawType(p.Type(), p.Value()) |
| + |
| + ret := ds.Property{} |
| + ret.SetValue(v, p.IndexSetting()) |
| + return ret |
| +} |
| + |
| +// RawType converts a decomposed Property into fields that represent its |
| +// primitive datastore storage format. |
| +// |
| +// The following conversions are in place: |
| +// - PTTime is converted to an microseconds-from-epoch integer value. |
| +// - PTBlobKey is converted to a string. |
|
iannucci
2015/12/29 21:41:48
and PTBytes too?
|
| +func RawType(t ds.PropertyType, v interface{}) (ds.PropertyType, interface{}) { |
| + switch t { |
| + case ds.PTTime: |
| + return ds.PTInt, timeToInt(v.(time.Time)) |
| + case ds.PTBlobKey: |
| + return ds.PTString, string(v.(blobstore.Key)) |
| + default: |
| + return t, v |
| + } |
| +} |
| + |
| // ReadProperty reads a Property from the buffer. `context`, `appid`, and |
| // `namespace` behave the same way they do for ReadKey, but only have an |
| // effect if the decoded property has a Key value. |
| @@ -504,35 +550,32 @@ func PropertyMapPartially(k *ds.Key, pm ds.PropertyMap) (ret SerializedPmap) { |
| } |
| func toBytesErr(i interface{}, ctx KeyContext) (ret []byte, err error) { |
| - buf := &bytes.Buffer{} |
| - switch x := i.(type) { |
| - case ds.GeoPoint: |
| - err = WriteGeoPoint(buf, x) |
| + buf := bytes.Buffer{} |
| + switch t := i.(type) { |
| case ds.IndexColumn: |
| - err = WriteIndexColumn(buf, x) |
| + err = WriteIndexColumn(&buf, t) |
| case ds.IndexDefinition: |
| - err = WriteIndexDefinition(buf, x) |
| - |
| - case *ds.Key: |
| - err = WriteKey(buf, ctx, x) |
| + err = WriteIndexDefinition(&buf, t) |
| case ds.KeyTok: |
| - err = WriteKeyTok(buf, x) |
| + err = WriteKeyTok(&buf, t) |
| case ds.Property: |
| - err = WriteProperty(buf, ctx, x) |
| + err = WriteProperty(&buf, ctx, t) |
| case ds.PropertyMap: |
| - err = WritePropertyMap(buf, ctx, x) |
| - |
| - case time.Time: |
| - err = WriteTime(buf, x) |
| + err = WritePropertyMap(&buf, ctx, t) |
| default: |
| - err = fmt.Errorf("unknown type for ToBytes: %T", i) |
| + var pt ds.PropertyType |
| + pt, err = ds.PropertyTypeOf(i, false) |
| + if err == nil { |
| + err = writeRawProperty(&buf, ctx, pt, t) |
| + } |
| } |
| + |
| if err == nil { |
| ret = buf.Bytes() |
| } |