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

Unified Diff: service/rawdatastore/serialize.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/serialize.go
diff --git a/service/rawdatastore/serialize.go b/service/rawdatastore/serialize.go
deleted file mode 100644
index 8fe3424367ecc6052b33ac5641eb7cac6491124a..0000000000000000000000000000000000000000
--- a/service/rawdatastore/serialize.go
+++ /dev/null
@@ -1,377 +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 (
- "bytes"
- "errors"
- "fmt"
- "sort"
- "time"
-
- "github.com/luci/gae/service/blobstore"
- "github.com/luci/luci-go/common/cmpbin"
-)
-
-// WritePropertyMapDeterministic allows tests to make WritePropertyMap
-// deterministic.
-var WritePropertyMapDeterministic = false
-
-// ReadPropertyMapReasonableLimit sets a limit on the number of rows and
-// number of properties per row which can be read by ReadPropertyMap. The
-// total number of Property objects readable by this method is this number
-// squared (e.g. Limit rows * Limit properties)
-const ReadPropertyMapReasonableLimit uint64 = 30000
-
-// ReadKeyNumToksReasonableLimit is the maximum number of Key tokens that
-// ReadKey is willing to read for a single key.
-const ReadKeyNumToksReasonableLimit uint64 = 50
-
-// KeyContext controls whether the various Write and Read serializtion
-// routines should encode the context of Keys (read: the appid and namespace).
-// Frequently the appid and namespace of keys are known in advance and so there's
-// no reason to redundantly encode them.
-type KeyContext bool
-
-// With- and WithoutContext indicate if the serialization method should include
-// context for Keys. See KeyContext for more information.
-const (
- WithContext KeyContext = true
- WithoutContext = false
-)
-
-// 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) {
- // [appid ++ namespace]? ++ #tokens ++ tokens*
- defer recoverTo(&err)
- appid, namespace, toks := KeySplit(k)
- if context == WithContext {
- panicIf(buf.WriteByte(1))
- _, e := cmpbin.WriteString(buf, appid)
- panicIf(e)
- _, e = cmpbin.WriteString(buf, namespace)
- panicIf(e)
- } else {
- panicIf(buf.WriteByte(0))
- }
- _, e := cmpbin.WriteUint(buf, uint64(len(toks)))
- panicIf(e)
- for _, tok := range toks {
- panicIf(WriteKeyTok(buf, tok))
- }
- return nil
-}
-
-// ReadKey deserializes a key from the buffer. The value of context must match
-// 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) {
- defer recoverTo(&err)
- actualCtx, e := buf.ReadByte()
- panicIf(e)
-
- actualAid, actualNS := "", ""
- if actualCtx == 1 {
- actualAid, _, e = cmpbin.ReadString(buf)
- panicIf(e)
- actualNS, _, e = cmpbin.ReadString(buf)
- panicIf(e)
- } else if actualCtx != 0 {
- err = fmt.Errorf("helper: expected actualCtx to be 0 or 1, got %d", actualCtx)
- return
- }
-
- if context == WithoutContext {
- // overrwrite with the supplied ones
- actualAid = appid
- actualNS = namespace
- }
-
- numToks, _, e := cmpbin.ReadUint(buf)
- panicIf(e)
- if numToks > ReadKeyNumToksReasonableLimit {
- err = fmt.Errorf("helper: tried to decode huge key of length %d", numToks)
- return
- }
-
- toks := make([]KeyTok, numToks)
- for i := uint64(0); i < numToks; i++ {
- toks[i], e = ReadKeyTok(buf)
- panicIf(e)
- }
-
- return NewKeyToks(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) {
- // 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)))
- _, e := cmpbin.WriteString(buf, tok.StringID)
- panicIf(e)
- } else {
- panicIf(buf.WriteByte(byte(PTInt)))
- _, e := cmpbin.WriteInt(buf, tok.IntID)
- panicIf(e)
- }
- return nil
-}
-
-// ReadKeyTok reads a KeyTok from the buffer. You usually want ReadKey
-// instead of this.
-func ReadKeyTok(buf Buffer) (ret KeyTok, err error) {
- defer recoverTo(&err)
- e := error(nil)
- ret.Kind, _, e = cmpbin.ReadString(buf)
- panicIf(e)
-
- typ, e := buf.ReadByte()
- panicIf(e)
-
- switch PropertyType(typ) {
- case PTString:
- ret.StringID, _, err = cmpbin.ReadString(buf)
- case 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))
- }
- return
-}
-
-// Write writes a GeoPoint to the buffer.
-func (gp GeoPoint) Write(buf Buffer) (err error) {
- defer recoverTo(&err)
- _, e := cmpbin.WriteFloat64(buf, gp.Lat)
- panicIf(e)
- _, e = cmpbin.WriteFloat64(buf, gp.Lng)
- return e
-}
-
-// Read reads a GeoPoint from the buffer.
-func (gp *GeoPoint) Read(buf Buffer) (err error) {
- defer recoverTo(&err)
- e := error(nil)
- gp.Lat, _, e = cmpbin.ReadFloat64(buf)
- panicIf(e)
-
- gp.Lng, _, e = cmpbin.ReadFloat64(buf)
- panicIf(e)
-
- if !gp.Valid() {
- err = fmt.Errorf("helper: decoded invalid GeoPoint: %v", gp)
- }
- return
-}
-
-// WriteTime writes a time.Time in a byte-sortable way.
-//
-// This method truncates the time to microseconds and drops the timezone,
-// because that's the (undocumented) way that the appengine SDK does it.
-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.WriteUint(buf, uint64(t.Unix())*1e6+uint64(t.Nanosecond()/1e3))
- return err
-}
-
-// ReadTime reads a time.Time from the buffer.
-func ReadTime(buf Buffer) (time.Time, error) {
- v, _, err := cmpbin.ReadUint(buf)
- if err != nil {
- return time.Time{}, err
- }
- return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil
-}
-
-// Write 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) {
- defer recoverTo(&err)
- typb := byte(p.Type())
- if p.IndexSetting() == NoIndex {
- typb |= 0x80
- }
- panicIf(buf.WriteByte(typb))
- switch p.Type() {
- case PTNull, PTBoolTrue, PTBoolFalse:
- case PTInt:
- _, err = cmpbin.WriteInt(buf, p.Value().(int64))
- case PTFloat:
- _, err = cmpbin.WriteFloat64(buf, p.Value().(float64))
- case PTString:
- _, err = cmpbin.WriteString(buf, p.Value().(string))
- case PTBytes:
- if p.IndexSetting() == NoIndex {
- _, err = cmpbin.WriteBytes(buf, p.Value().([]byte))
- } else {
- _, err = cmpbin.WriteBytes(buf, p.Value().(ByteString))
- }
- case 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:
- _, err = cmpbin.WriteString(buf, string(p.Value().(blobstore.Key)))
- }
- return
-}
-
-// Read 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) {
- val := interface{}(nil)
- typb, err := buf.ReadByte()
- if err != nil {
- return
- }
- is := ShouldIndex
- if (typb & 0x80) != 0 {
- is = NoIndex
- }
- switch PropertyType(typb & 0x7f) {
- case PTNull:
- case PTBoolTrue:
- val = true
- case PTBoolFalse:
- val = false
- case PTInt:
- val, _, err = cmpbin.ReadInt(buf)
- case PTFloat:
- val, _, err = cmpbin.ReadFloat64(buf)
- case PTString:
- val, _, err = cmpbin.ReadString(buf)
- case PTBytes:
- b := []byte(nil)
- if b, _, err = cmpbin.ReadBytes(buf); err != nil {
- break
- }
- if is == NoIndex {
- val = b
- } else {
- val = ByteString(b)
- }
- case PTTime:
- val, err = ReadTime(buf)
- case PTGeoPoint:
- gp := GeoPoint{}
- err = gp.Read(buf)
- val = gp
- case PTKey:
- val, err = ReadKey(buf, context, appid, namespace)
- case PTBlobKey:
- s := ""
- if s, _, err = cmpbin.ReadString(buf); err != nil {
- break
- }
- val = blobstore.Key(s)
- default:
- err = fmt.Errorf("read: unknown type! %v", typb)
- }
- if err == nil {
- err = p.SetValue(val, is)
- }
- return
-}
-
-// Write 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).
-func (pm PropertyMap) Write(buf Buffer, context KeyContext) (err error) {
- defer recoverTo(&err)
- rows := make(sort.StringSlice, 0, len(pm))
- tmpBuf := &bytes.Buffer{}
- for name, vals := range pm {
- 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))
- }
- rows = append(rows, tmpBuf.String())
- }
-
- if WritePropertyMapDeterministic {
- rows.Sort()
- }
-
- _, e := cmpbin.WriteUint(buf, uint64(len(pm)))
- panicIf(e)
- for _, r := range rows {
- _, e := buf.WriteString(r)
- panicIf(e)
- }
- return
-}
-
-// Read 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) {
- defer recoverTo(&err)
-
- numRows := uint64(0)
- numRows, _, e := cmpbin.ReadUint(buf)
- panicIf(e)
- if numRows > ReadPropertyMapReasonableLimit {
- err = fmt.Errorf("helper: tried to decode map with huge number of rows %d", numRows)
- return
- }
-
- name, prop := "", Property{}
- for i := uint64(0); i < numRows; i++ {
- name, _, e = cmpbin.ReadString(buf)
- panicIf(e)
-
- numProps, _, e := cmpbin.ReadUint(buf)
- panicIf(e)
- if numProps > ReadPropertyMapReasonableLimit {
- err = fmt.Errorf("helper: tried to decode map with huge number of properties %d", numProps)
- return
- }
- props := make([]Property, 0, numProps)
- for j := uint64(0); j < numProps; j++ {
- panicIf(prop.Read(buf, context, appid, namespace))
- props = append(props, prop)
- }
- pm[name] = props
- }
- return
-}
-
-type parseError error
-
-func panicIf(err error) {
- if err != nil {
- panic(parseError(err))
- }
-}
-
-func recoverTo(err *error) {
- if r := recover(); r != nil {
- if rerr := r.(parseError); rerr != nil {
- *err = error(rerr)
- }
- }
-}

Powered by Google App Engine
This is Rietveld 408576698