Index: service/datastore/serialize/serialize.go |
diff --git a/service/datastore/serialize.go b/service/datastore/serialize/serialize.go |
similarity index 61% |
rename from service/datastore/serialize.go |
rename to service/datastore/serialize/serialize.go |
index e9117ababcf2e4fea3a4506f010144181715763b..bfcc78ce9c7a0507bd083292e5100185165f2421 100644 |
--- a/service/datastore/serialize.go |
+++ b/service/datastore/serialize/serialize.go |
@@ -2,7 +2,7 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-package datastore |
+package serialize |
import ( |
"bytes" |
@@ -12,11 +12,14 @@ import ( |
"time" |
"github.com/luci/gae/service/blobstore" |
+ ds "github.com/luci/gae/service/datastore" |
+ "github.com/luci/gae/service/datastore/dskey" |
"github.com/luci/luci-go/common/cmpbin" |
) |
-// MaxIndexColumns is the maximum number of sort orders you may have on a |
-// single composite index. 64 was chosen as a likely-astronomical number. |
+// MaxIndexColumns is the maximum number of sort columns (e.g. sort orders) that |
+// ReadIndexDefinition is willing to deserialize. 64 was chosen as |
+// a likely-astronomical number. |
const MaxIndexColumns = 64 |
// WritePropertyMapDeterministic allows tests to make WritePropertyMap |
@@ -48,10 +51,10 @@ const ( |
// WriteKey encodes a key to the buffer. If context is WithContext, then this |
// encoded value will include the appid and namespace of the key. |
-func WriteKey(buf Buffer, context KeyContext, k Key) (err error) { |
+func WriteKey(buf Buffer, context KeyContext, k ds.Key) (err error) { |
// [appid ++ namespace]? ++ #tokens ++ tokens* |
defer recoverTo(&err) |
- appid, namespace, toks := KeySplit(k) |
+ appid, namespace, toks := dskey.Split(k) |
if context == WithContext { |
panicIf(buf.WriteByte(1)) |
_, e := cmpbin.WriteString(buf, appid) |
@@ -73,7 +76,7 @@ func WriteKey(buf Buffer, context KeyContext, k Key) (err error) { |
// the value of context that was passed to WriteKey when the key was encoded. |
// If context == WithoutContext, then the appid and namespace parameters are |
// used in the decoded Key. Otherwise they're ignored. |
-func ReadKey(buf Buffer, context KeyContext, appid, namespace string) (ret Key, err error) { |
+func ReadKey(buf Buffer, context KeyContext, appid, namespace string) (ret ds.Key, err error) { |
defer recoverTo(&err) |
actualCtx, e := buf.ReadByte() |
panicIf(e) |
@@ -102,28 +105,28 @@ func ReadKey(buf Buffer, context KeyContext, appid, namespace string) (ret Key, |
return |
} |
- toks := make([]KeyTok, numToks) |
+ toks := make([]ds.KeyTok, numToks) |
for i := uint64(0); i < numToks; i++ { |
toks[i], e = ReadKeyTok(buf) |
panicIf(e) |
} |
- return NewKeyToks(actualAid, actualNS, toks), nil |
+ return dskey.NewToks(actualAid, actualNS, toks), nil |
} |
// WriteKeyTok writes a KeyTok to the buffer. You usually want WriteKey |
// instead of this. |
-func WriteKeyTok(buf Buffer, tok KeyTok) (err error) { |
+func WriteKeyTok(buf Buffer, tok ds.KeyTok) (err error) { |
// tok.kind ++ typ ++ [tok.stringID || tok.intID] |
defer recoverTo(&err) |
_, e := cmpbin.WriteString(buf, tok.Kind) |
panicIf(e) |
if tok.StringID != "" { |
- panicIf(buf.WriteByte(byte(PTString))) |
+ panicIf(buf.WriteByte(byte(ds.PTString))) |
_, e := cmpbin.WriteString(buf, tok.StringID) |
panicIf(e) |
} else { |
- panicIf(buf.WriteByte(byte(PTInt))) |
+ panicIf(buf.WriteByte(byte(ds.PTInt))) |
_, e := cmpbin.WriteInt(buf, tok.IntID) |
panicIf(e) |
} |
@@ -132,7 +135,7 @@ func WriteKeyTok(buf Buffer, tok KeyTok) (err error) { |
// ReadKeyTok reads a KeyTok from the buffer. You usually want ReadKey |
// instead of this. |
-func ReadKeyTok(buf Buffer) (ret KeyTok, err error) { |
+func ReadKeyTok(buf Buffer) (ret ds.KeyTok, err error) { |
defer recoverTo(&err) |
e := error(nil) |
ret.Kind, _, e = cmpbin.ReadString(buf) |
@@ -141,22 +144,22 @@ func ReadKeyTok(buf Buffer) (ret KeyTok, err error) { |
typ, e := buf.ReadByte() |
panicIf(e) |
- switch PropertyType(typ) { |
- case PTString: |
+ switch ds.PropertyType(typ) { |
+ case ds.PTString: |
ret.StringID, _, err = cmpbin.ReadString(buf) |
- case PTInt: |
+ case ds.PTInt: |
ret.IntID, _, err = cmpbin.ReadInt(buf) |
if err == nil && ret.IntID <= 0 { |
err = errors.New("helper: decoded key with empty stringID and zero/negative intID") |
} |
default: |
- err = fmt.Errorf("helper: invalid type %s", PropertyType(typ)) |
+ err = fmt.Errorf("helper: invalid type %s", ds.PropertyType(typ)) |
} |
return |
} |
-// Write writes a GeoPoint to the buffer. |
-func (gp GeoPoint) Write(buf Buffer) (err error) { |
+// WriteGeoPoint writes a GeoPoint to the buffer. |
+func WriteGeoPoint(buf Buffer, gp ds.GeoPoint) (err error) { |
defer recoverTo(&err) |
_, e := cmpbin.WriteFloat64(buf, gp.Lat) |
panicIf(e) |
@@ -164,8 +167,8 @@ func (gp GeoPoint) Write(buf Buffer) (err error) { |
return e |
} |
-// Read reads a GeoPoint from the buffer. |
-func (gp *GeoPoint) Read(buf Buffer) (err error) { |
+// ReadGeoPoint reads a GeoPoint from the buffer. |
+func ReadGeoPoint(buf Buffer) (gp ds.GeoPoint, err error) { |
defer recoverTo(&err) |
e := error(nil) |
gp.Lat, _, e = cmpbin.ReadFloat64(buf) |
@@ -202,86 +205,84 @@ func ReadTime(buf Buffer) (time.Time, error) { |
return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil |
} |
-// Write writes a Property to the buffer. `context` behaves the same |
+// WriteProperty writes a Property to the buffer. `context` behaves the same |
// way that it does for WriteKey, but only has an effect if `p` contains a |
// Key as its Value. |
-func (p *Property) Write(buf Buffer, context KeyContext) (err error) { |
+func WriteProperty(buf Buffer, context KeyContext, p ds.Property) (err error) { |
defer recoverTo(&err) |
typb := byte(p.Type()) |
- if p.IndexSetting() == NoIndex { |
+ if p.IndexSetting() == ds.NoIndex { |
typb |= 0x80 |
} |
panicIf(buf.WriteByte(typb)) |
switch p.Type() { |
- case PTNull, PTBoolTrue, PTBoolFalse: |
- case PTInt: |
+ case ds.PTNull, ds.PTBoolTrue, ds.PTBoolFalse: |
+ case ds.PTInt: |
_, err = cmpbin.WriteInt(buf, p.Value().(int64)) |
- case PTFloat: |
+ case ds.PTFloat: |
_, err = cmpbin.WriteFloat64(buf, p.Value().(float64)) |
- case PTString: |
+ case ds.PTString: |
_, err = cmpbin.WriteString(buf, p.Value().(string)) |
- case PTBytes: |
- if p.IndexSetting() == NoIndex { |
+ case ds.PTBytes: |
+ if p.IndexSetting() == ds.NoIndex { |
_, err = cmpbin.WriteBytes(buf, p.Value().([]byte)) |
} else { |
- _, err = cmpbin.WriteBytes(buf, p.Value().(ByteString)) |
+ _, err = cmpbin.WriteBytes(buf, p.Value().(ds.ByteString)) |
} |
- case PTTime: |
+ case ds.PTTime: |
err = WriteTime(buf, p.Value().(time.Time)) |
- case PTGeoPoint: |
- err = p.Value().(GeoPoint).Write(buf) |
- case PTKey: |
- err = WriteKey(buf, context, p.Value().(Key)) |
- case PTBlobKey: |
+ case ds.PTGeoPoint: |
+ err = WriteGeoPoint(buf, p.Value().(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))) |
} |
return |
} |
-// Read reads a Property from the buffer. `context`, `appid`, and |
+// 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. |
-func (p *Property) Read(buf Buffer, context KeyContext, appid, namespace string) (err error) { |
+func ReadProperty(buf Buffer, context KeyContext, appid, namespace string) (p ds.Property, err error) { |
val := interface{}(nil) |
typb, err := buf.ReadByte() |
if err != nil { |
return |
} |
- is := ShouldIndex |
+ is := ds.ShouldIndex |
if (typb & 0x80) != 0 { |
- is = NoIndex |
+ is = ds.NoIndex |
} |
- switch PropertyType(typb & 0x7f) { |
- case PTNull: |
- case PTBoolTrue: |
+ switch ds.PropertyType(typb & 0x7f) { |
+ case ds.PTNull: |
+ case ds.PTBoolTrue: |
val = true |
- case PTBoolFalse: |
+ case ds.PTBoolFalse: |
val = false |
- case PTInt: |
+ case ds.PTInt: |
val, _, err = cmpbin.ReadInt(buf) |
- case PTFloat: |
+ case ds.PTFloat: |
val, _, err = cmpbin.ReadFloat64(buf) |
- case PTString: |
+ case ds.PTString: |
val, _, err = cmpbin.ReadString(buf) |
- case PTBytes: |
+ case ds.PTBytes: |
b := []byte(nil) |
if b, _, err = cmpbin.ReadBytes(buf); err != nil { |
break |
} |
- if is == NoIndex { |
+ if is == ds.NoIndex { |
val = b |
} else { |
- val = ByteString(b) |
+ val = ds.ByteString(b) |
} |
- case PTTime: |
+ case ds.PTTime: |
val, err = ReadTime(buf) |
- case PTGeoPoint: |
- gp := GeoPoint{} |
- err = gp.Read(buf) |
- val = gp |
- case PTKey: |
+ case ds.PTGeoPoint: |
+ val, err = ReadGeoPoint(buf) |
+ case ds.PTKey: |
val, err = ReadKey(buf, context, appid, namespace) |
- case PTBlobKey: |
+ case ds.PTBlobKey: |
s := "" |
if s, _, err = cmpbin.ReadString(buf); err != nil { |
break |
@@ -296,28 +297,26 @@ func (p *Property) Read(buf Buffer, context KeyContext, appid, namespace string) |
return |
} |
-// Write writes an entire PropertyMap to the buffer. `context` behaves the same |
+// WritePropertyMap writes an entire PropertyMap to the buffer. `context` behaves the same |
// way that it does for WriteKey. If WritePropertyMapDeterministic is true, then |
// the rows will be sorted by property name before they're serialized to buf |
// (mostly useful for testing, but also potentially useful if you need to make |
// a hash of the property data). |
// |
// Write skips metadata keys. |
-func (pm PropertyMap) Write(buf Buffer, context KeyContext) (err error) { |
+func WritePropertyMap(buf Buffer, context KeyContext, pm ds.PropertyMap) (err error) { |
defer recoverTo(&err) |
rows := make(sort.StringSlice, 0, len(pm)) |
tmpBuf := &bytes.Buffer{} |
+ pm, _ = pm.Save(false) |
for name, vals := range pm { |
- if isMetaKey(name) { |
- continue |
- } |
tmpBuf.Reset() |
_, e := cmpbin.WriteString(tmpBuf, name) |
panicIf(e) |
_, e = cmpbin.WriteUint(tmpBuf, uint64(len(vals))) |
panicIf(e) |
for _, p := range vals { |
- panicIf(p.Write(tmpBuf, context)) |
+ panicIf(WriteProperty(tmpBuf, context, p)) |
} |
rows = append(rows, tmpBuf.String()) |
} |
@@ -335,9 +334,9 @@ func (pm PropertyMap) Write(buf Buffer, context KeyContext) (err error) { |
return |
} |
-// Read reads a PropertyMap from the buffer. `context` and |
+// ReadPropertyMap reads a PropertyMap from the buffer. `context` and |
// friends behave the same way that they do for ReadKey. |
-func (pm PropertyMap) Read(buf Buffer, context KeyContext, appid, namespace string) (err error) { |
+func ReadPropertyMap(buf Buffer, context KeyContext, appid, namespace string) (pm ds.PropertyMap, err error) { |
defer recoverTo(&err) |
numRows := uint64(0) |
@@ -348,7 +347,9 @@ func (pm PropertyMap) Read(buf Buffer, context KeyContext, appid, namespace stri |
return |
} |
- name, prop := "", Property{} |
+ pm = make(ds.PropertyMap, numRows) |
+ |
+ name, prop := "", ds.Property{} |
for i := uint64(0); i < numRows; i++ { |
name, _, e = cmpbin.ReadString(buf) |
panicIf(e) |
@@ -359,9 +360,10 @@ func (pm PropertyMap) Read(buf Buffer, context KeyContext, appid, namespace stri |
err = fmt.Errorf("helper: tried to decode map with huge number of properties %d", numProps) |
return |
} |
- props := make([]Property, 0, numProps) |
+ props := make([]ds.Property, 0, numProps) |
for j := uint64(0); j < numProps; j++ { |
- panicIf(prop.Read(buf, context, appid, namespace)) |
+ prop, err = ReadProperty(buf, context, appid, namespace) |
+ panicIf(err) |
props = append(props, prop) |
} |
pm[name] = props |
@@ -369,10 +371,11 @@ func (pm PropertyMap) Read(buf Buffer, context KeyContext, appid, namespace stri |
return |
} |
-func (c *IndexColumn) Write(buf Buffer) (err error) { |
+// WriteIndexColumn writes an IndexColumn to the buffer. |
+func WriteIndexColumn(buf Buffer, c ds.IndexColumn) (err error) { |
defer recoverTo(&err) |
- if c.Direction == ASCENDING { |
+ if c.Direction == ds.ASCENDING { |
panicIf(buf.WriteByte(0)) |
} else { |
panicIf(buf.WriteByte(1)) |
@@ -381,7 +384,8 @@ func (c *IndexColumn) Write(buf Buffer) (err error) { |
return |
} |
-func (c *IndexColumn) Read(buf Buffer) (err error) { |
+// ReadIndexColumn reads an IndexColumn from the buffer. |
+func ReadIndexColumn(buf Buffer) (c ds.IndexColumn, err error) { |
defer recoverTo(&err) |
dir, err := buf.ReadByte() |
@@ -389,15 +393,16 @@ func (c *IndexColumn) Read(buf Buffer) (err error) { |
switch dir { |
case 0: |
- c.Direction = ASCENDING |
+ c.Direction = ds.ASCENDING |
default: |
- c.Direction = DESCENDING |
+ c.Direction = ds.DESCENDING |
} |
c.Property, _, err = cmpbin.ReadString(buf) |
- return err |
+ return |
} |
-func (i *IndexDefinition) Write(buf Buffer) (err error) { |
+// WriteIndexDefinition writes an IndexDefinition to the buffer |
+func WriteIndexDefinition(buf Buffer, i ds.IndexDefinition) (err error) { |
defer recoverTo(&err) |
if i.Builtin() { |
@@ -415,12 +420,13 @@ func (i *IndexDefinition) Write(buf Buffer) (err error) { |
_, err = cmpbin.WriteUint(buf, uint64(len(i.SortBy))) |
panicIf(err) |
for _, sb := range i.SortBy { |
- panicIf(sb.Write(buf)) |
+ panicIf(WriteIndexColumn(buf, sb)) |
} |
return |
} |
-func (i *IndexDefinition) Read(buf Buffer) (err error) { |
+// ReadIndexDefinition reads an IndexDefinition from the buffer. |
+func ReadIndexDefinition(buf Buffer) (i ds.IndexDefinition, err error) { |
defer recoverTo(&err) |
// discard builtin/complex byte |
@@ -439,20 +445,104 @@ func (i *IndexDefinition) Read(buf Buffer) (err error) { |
panicIf(err) |
if numSorts > MaxIndexColumns { |
- return fmt.Errorf("datastore: Got over %d sort orders: %d", |
+ err = fmt.Errorf("datastore: Got over %d sort orders: %d", |
MaxIndexColumns, numSorts) |
+ return |
} |
if numSorts > 0 { |
- i.SortBy = make([]IndexColumn, numSorts) |
+ i.SortBy = make([]ds.IndexColumn, numSorts) |
for idx := range i.SortBy { |
- panicIf(i.SortBy[idx].Read(buf)) |
+ i.SortBy[idx], err = ReadIndexColumn(buf) |
+ panicIf(err) |
} |
} |
return |
} |
+func toBytesErr(i interface{}, ctx KeyContext) (ret []byte, err error) { |
+ buf := &bytes.Buffer{} |
+ switch x := i.(type) { |
+ case ds.GeoPoint: |
+ err = WriteGeoPoint(buf, x) |
+ |
+ case ds.IndexColumn: |
+ err = WriteIndexColumn(buf, x) |
+ |
+ case ds.IndexDefinition: |
+ err = WriteIndexDefinition(buf, x) |
+ |
+ case ds.Key: |
+ err = WriteKey(buf, ctx, x) |
+ |
+ case ds.KeyTok: |
+ err = WriteKeyTok(buf, x) |
+ |
+ case ds.Property: |
+ err = WriteProperty(buf, ctx, x) |
+ |
+ case ds.PropertyMap: |
+ err = WritePropertyMap(buf, ctx, x) |
+ |
+ case time.Time: |
+ err = WriteTime(buf, x) |
+ |
+ default: |
+ err = fmt.Errorf("unknown type for ToBytes: %T", i) |
+ } |
+ if err == nil { |
+ ret = buf.Bytes() |
+ } |
+ return |
+} |
+ |
+// ToBytesErr serializes i to a byte slice, if it's one of the type supported |
+// by this library, otherwise it returns an error. |
+// |
+// Key types will be serialized using the 'WithoutContext' option (e.g. their |
+// encoded forms will not contain AppID or Namespace). |
+func ToBytesErr(i interface{}) ([]byte, error) { |
+ return toBytesErr(i, WithoutContext) |
+} |
+ |
+// ToBytesWithContextErr serializes i to a byte slice, if it's one of the type |
+// supported by this library, otherwise it returns an error. |
+// |
+// Key types will be serialized using the 'WithContext' option (e.g. their |
+// encoded forms will contain AppID and Namespace). |
+func ToBytesWithContextErr(i interface{}) ([]byte, error) { |
+ return toBytesErr(i, WithContext) |
+} |
+ |
+// ToBytes serializes i to a byte slice, if it's one of the type supported |
+// by this library. If an error is encountered (e.g. `i` is not a supported |
+// type), this method panics. |
+// |
+// Key types will be serialized using the 'WithoutContext' option (e.g. their |
+// encoded forms will not contain AppID or Namespace). |
+func ToBytes(i interface{}) []byte { |
+ ret, err := ToBytesErr(i) |
+ if err != nil { |
+ panic(err) |
+ } |
+ return ret |
+} |
+ |
+// ToBytesWithContext serializes i to a byte slice, if it's one of the type |
+// supported by this library. If an error is encountered (e.g. `i` is not |
+// a supported type), this method panics. |
+// |
+// Key types will be serialized using the 'WithContext' option (e.g. their |
+// encoded forms will not contain AppID or Namespace). |
+func ToBytesWithContext(i interface{}) []byte { |
+ ret, err := ToBytesWithContextErr(i) |
+ if err != nil { |
+ panic(err) |
+ } |
+ return ret |
+} |
+ |
type parseError error |
func panicIf(err error) { |