| Index: go/src/infra/gae/libs/wrapper/memory/plist.go
 | 
| diff --git a/go/src/infra/gae/libs/wrapper/memory/plist.go b/go/src/infra/gae/libs/wrapper/memory/plist.go
 | 
| deleted file mode 100644
 | 
| index 73908b358612f88325f79ba7b3a0a1ef78c70b84..0000000000000000000000000000000000000000
 | 
| --- a/go/src/infra/gae/libs/wrapper/memory/plist.go
 | 
| +++ /dev/null
 | 
| @@ -1,637 +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 memory
 | 
| -
 | 
| -import (
 | 
| -	"bytes"
 | 
| -	"fmt"
 | 
| -	"reflect"
 | 
| -	"sort"
 | 
| -	"time"
 | 
| -
 | 
| -	"appengine"
 | 
| -	"appengine/datastore"
 | 
| -
 | 
| -	"github.com/luci/gkvlite"
 | 
| -	"github.com/luci/luci-go/common/cmpbin"
 | 
| -)
 | 
| -
 | 
| -type typData struct {
 | 
| -	noIndex bool
 | 
| -	typ     propValType
 | 
| -	data    interface{}
 | 
| -}
 | 
| -
 | 
| -func newTypData(noIndex bool, v interface{}) (ret *typData, err error) {
 | 
| -	typ := pvUNKNOWN
 | 
| -
 | 
| -	switch x := v.(type) {
 | 
| -	case nil:
 | 
| -		typ = pvNull
 | 
| -	case time.Time:
 | 
| -		typ = pvTime
 | 
| -	case int64:
 | 
| -		typ = pvInt
 | 
| -	case float64:
 | 
| -		typ = pvFloat
 | 
| -	case bool:
 | 
| -		if x {
 | 
| -			typ = pvBoolTrue
 | 
| -		} else {
 | 
| -			typ = pvBoolFalse
 | 
| -		}
 | 
| -	case []byte, datastore.ByteString:
 | 
| -		typ = pvBytes
 | 
| -	case appengine.BlobKey:
 | 
| -		typ = pvBlobKey
 | 
| -	case string:
 | 
| -		typ = pvStr
 | 
| -	case appengine.GeoPoint:
 | 
| -		typ = pvGeoPoint
 | 
| -	case *datastore.Key:
 | 
| -		typ = pvKey
 | 
| -	}
 | 
| -	if typ == pvUNKNOWN {
 | 
| -		err = fmt.Errorf("propValTypeOf: unknown type of %T: %#v", v, v)
 | 
| -	}
 | 
| -
 | 
| -	return &typData{noIndex, typ, v}, err
 | 
| -}
 | 
| -
 | 
| -func (td *typData) WriteBinary(buf *bytes.Buffer, nso nsOption) error {
 | 
| -	typb := byte(td.typ)
 | 
| -	if td.noIndex {
 | 
| -		typb |= 0x80
 | 
| -	}
 | 
| -	buf.WriteByte(typb)
 | 
| -	switch td.typ {
 | 
| -	case pvNull, pvBoolFalse, pvBoolTrue:
 | 
| -		return nil
 | 
| -	case pvInt:
 | 
| -		cmpbin.WriteInt(buf, td.data.(int64))
 | 
| -	case pvFloat:
 | 
| -		writeFloat64(buf, td.data.(float64))
 | 
| -	case pvStr:
 | 
| -		writeString(buf, td.data.(string))
 | 
| -	case pvBytes:
 | 
| -		if td.noIndex {
 | 
| -			writeBytes(buf, td.data.([]byte))
 | 
| -		} else {
 | 
| -			writeBytes(buf, td.data.(datastore.ByteString))
 | 
| -		}
 | 
| -	case pvTime:
 | 
| -		writeTime(buf, td.data.(time.Time))
 | 
| -	case pvGeoPoint:
 | 
| -		writeGeoPoint(buf, td.data.(appengine.GeoPoint))
 | 
| -	case pvKey:
 | 
| -		writeKey(buf, nso, td.data.(*datastore.Key))
 | 
| -	case pvBlobKey:
 | 
| -		writeString(buf, string(td.data.(appengine.BlobKey)))
 | 
| -	default:
 | 
| -		return fmt.Errorf("write: unknown type! %v", td)
 | 
| -	}
 | 
| -	return nil
 | 
| -}
 | 
| -
 | 
| -func (td *typData) ReadBinary(buf *bytes.Buffer, nso nsOption, ns string) error {
 | 
| -	typb, err := buf.ReadByte()
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -	td.noIndex = (typb & 0x80) != 0 // highbit means noindex
 | 
| -	td.typ = propValType(typb & 0x7f)
 | 
| -	switch td.typ {
 | 
| -	case pvNull:
 | 
| -		td.data = nil
 | 
| -	case pvBoolTrue:
 | 
| -		td.data = true
 | 
| -	case pvBoolFalse:
 | 
| -		td.data = false
 | 
| -	case pvInt:
 | 
| -		td.data, _, err = cmpbin.ReadInt(buf)
 | 
| -	case pvFloat:
 | 
| -		td.data, err = readFloat64(buf)
 | 
| -	case pvStr:
 | 
| -		td.data, err = readString(buf)
 | 
| -	case pvBytes:
 | 
| -		b := []byte(nil)
 | 
| -		if b, err = readBytes(buf); err != nil {
 | 
| -			return err
 | 
| -		}
 | 
| -		if td.noIndex {
 | 
| -			td.data = b
 | 
| -		} else {
 | 
| -			td.data = datastore.ByteString(b)
 | 
| -		}
 | 
| -	case pvTime:
 | 
| -		td.data, err = readTime(buf)
 | 
| -	case pvGeoPoint:
 | 
| -		td.data, err = readGeoPoint(buf)
 | 
| -	case pvKey:
 | 
| -		td.data, err = readKey(buf, nso, ns)
 | 
| -	case pvBlobKey:
 | 
| -		s := ""
 | 
| -		if s, err = readString(buf); err != nil {
 | 
| -			return err
 | 
| -		}
 | 
| -		td.data = appengine.BlobKey(s)
 | 
| -	default:
 | 
| -		return fmt.Errorf("read: unknown type! %v", td)
 | 
| -	}
 | 
| -
 | 
| -	return err
 | 
| -}
 | 
| -
 | 
| -type pvals struct {
 | 
| -	name string
 | 
| -	vals []*typData
 | 
| -}
 | 
| -
 | 
| -type propertyList []datastore.Property
 | 
| -
 | 
| -var _ = datastore.PropertyLoadSaver((*propertyList)(nil))
 | 
| -
 | 
| -func (pl *propertyList) Load(ch <-chan datastore.Property) error {
 | 
| -	return (*datastore.PropertyList)(pl).Load(ch)
 | 
| -}
 | 
| -
 | 
| -func (pl *propertyList) Save(ch chan<- datastore.Property) error {
 | 
| -	return (*datastore.PropertyList)(pl).Save(ch)
 | 
| -}
 | 
| -
 | 
| -// collatedProperties is the reduction of a *propertyList such that each entry
 | 
| -// in a collatedProperties has a unique name. For example, collating this:
 | 
| -//   pl := &propertyList{
 | 
| -//     datastore.Property{Name: "wat", Val: "hello"},
 | 
| -//     datastore.Property{Name: "other", Val: 100},
 | 
| -//     datastore.Property{Name: "wat", Val: "goodbye", noIndex: true},
 | 
| -//   }
 | 
| -//
 | 
| -// Would get a collatedProperties which looked like:
 | 
| -//   c := collatedProperties{
 | 
| -//     &pvals{"wat",   []*typData{&{false, pvStr, "hello"},
 | 
| -//                                &{true,  pvStr, "goodbye"}}},
 | 
| -//     &pvals{"other", []*typData{&{false, pvInt, 100}}}
 | 
| -//   }
 | 
| -type collatedProperties []*pvals
 | 
| -
 | 
| -func (c collatedProperties) defaultIndicies(kind string) []*qIndex {
 | 
| -	ret := make([]*qIndex, 0, 2*len(c)+1)
 | 
| -	ret = append(ret, &qIndex{kind, false, nil})
 | 
| -	for _, pvals := range c {
 | 
| -		needsIndex := false
 | 
| -		for _, v := range pvals.vals {
 | 
| -			if !v.noIndex {
 | 
| -				needsIndex = true
 | 
| -				break
 | 
| -			}
 | 
| -		}
 | 
| -		if !needsIndex {
 | 
| -			continue
 | 
| -		}
 | 
| -		ret = append(ret, &qIndex{kind, false, []qSortBy{{pvals.name, qASC}}})
 | 
| -		ret = append(ret, &qIndex{kind, false, []qSortBy{{pvals.name, qDEC}}})
 | 
| -	}
 | 
| -	return ret
 | 
| -}
 | 
| -
 | 
| -// serializedPval is a single pvals.vals entry which has been serialized (in
 | 
| -// qASC order).
 | 
| -type serializedPval []byte
 | 
| -
 | 
| -// serializedPvals is all of the pvals.vals entries from a single pvals (in qASC
 | 
| -// order). It does not include the pvals.name field.
 | 
| -type serializedPvals []serializedPval
 | 
| -
 | 
| -func (s serializedPvals) Len() int           { return len(s) }
 | 
| -func (s serializedPvals) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 | 
| -func (s serializedPvals) Less(i, j int) bool { return bytes.Compare(s[i], s[j]) < 0 }
 | 
| -
 | 
| -type mappedPlist map[string]serializedPvals
 | 
| -
 | 
| -func (c collatedProperties) indexableMap() (mappedPlist, error) {
 | 
| -	ret := make(mappedPlist, len(c))
 | 
| -	for _, pv := range c {
 | 
| -		data := make(serializedPvals, 0, len(pv.vals))
 | 
| -		for _, v := range pv.vals {
 | 
| -			if v.noIndex {
 | 
| -				continue
 | 
| -			}
 | 
| -			buf := &bytes.Buffer{}
 | 
| -			if err := v.WriteBinary(buf, noNS); err != nil {
 | 
| -				return nil, err
 | 
| -			}
 | 
| -			data = append(data, buf.Bytes())
 | 
| -		}
 | 
| -		if len(data) == 0 {
 | 
| -			continue
 | 
| -		}
 | 
| -		sort.Sort(data)
 | 
| -		ret[pv.name] = data
 | 
| -	}
 | 
| -	return ret, nil
 | 
| -}
 | 
| -
 | 
| -// indexRowGen contains enough information to generate all of the index rows which
 | 
| -// correspond with a propertyList and a qIndex.
 | 
| -type indexRowGen struct {
 | 
| -	propVec []serializedPvals
 | 
| -	orders  []qDirection
 | 
| -}
 | 
| -
 | 
| -// permute calls cb for each index row, in the sorted order of the rows.
 | 
| -func (s indexRowGen) permute(cb func([]byte)) {
 | 
| -	iVec := make([]int, len(s.propVec))
 | 
| -	iVecLim := make([]int, len(s.propVec))
 | 
| -
 | 
| -	incPos := func() bool {
 | 
| -		for i := len(iVec) - 1; i >= 0; i-- {
 | 
| -			var done bool
 | 
| -			var newVal int
 | 
| -			if s.orders[i] == qASC {
 | 
| -				newVal = (iVec[i] + 1) % iVecLim[i]
 | 
| -				done = newVal != 0
 | 
| -			} else {
 | 
| -				newVal = (iVec[i] - 1)
 | 
| -				if newVal < 0 {
 | 
| -					newVal = iVecLim[i] - 1
 | 
| -				} else {
 | 
| -					done = true
 | 
| -				}
 | 
| -			}
 | 
| -			iVec[i] = newVal
 | 
| -			if done {
 | 
| -				return true
 | 
| -			}
 | 
| -		}
 | 
| -		return false
 | 
| -	}
 | 
| -
 | 
| -	for i, sps := range s.propVec {
 | 
| -		iVecLim[i] = len(sps)
 | 
| -	}
 | 
| -
 | 
| -	for i := range iVec {
 | 
| -		if s.orders[i] == qDEC {
 | 
| -			iVec[i] = iVecLim[i] - 1
 | 
| -		}
 | 
| -	}
 | 
| -
 | 
| -	for {
 | 
| -		bufsiz := 0
 | 
| -		for pvalSliceIdx, pvalIdx := range iVec {
 | 
| -			bufsiz += len(s.propVec[pvalSliceIdx][pvalIdx])
 | 
| -		}
 | 
| -		buf := bytes.NewBuffer(make([]byte, 0, bufsiz))
 | 
| -		for pvalSliceIdx, pvalIdx := range iVec {
 | 
| -			data := s.propVec[pvalSliceIdx][pvalIdx]
 | 
| -			if s.orders[pvalSliceIdx] == qASC {
 | 
| -				buf.Write(data)
 | 
| -			} else {
 | 
| -				for _, b := range data {
 | 
| -					buf.WriteByte(b ^ 0xFF)
 | 
| -				}
 | 
| -			}
 | 
| -		}
 | 
| -		cb(buf.Bytes())
 | 
| -		if !incPos() {
 | 
| -			break
 | 
| -		}
 | 
| -	}
 | 
| -}
 | 
| -
 | 
| -type matcher struct {
 | 
| -	buf indexRowGen
 | 
| -}
 | 
| -
 | 
| -// matcher.match checks to see if the mapped, serialized property values
 | 
| -// match the index. If they do, it returns a indexRowGen. Do not write or modify
 | 
| -// the data in the indexRowGen.
 | 
| -func (m *matcher) match(idx *qIndex, mpvals mappedPlist) (indexRowGen, bool) {
 | 
| -	m.buf.propVec = m.buf.propVec[:0]
 | 
| -	m.buf.orders = m.buf.orders[:0]
 | 
| -	for _, sb := range idx.sortby {
 | 
| -		if pv, ok := mpvals[sb.prop]; ok {
 | 
| -			m.buf.propVec = append(m.buf.propVec, pv)
 | 
| -			m.buf.orders = append(m.buf.orders, sb.dir)
 | 
| -		} else {
 | 
| -			return indexRowGen{}, false
 | 
| -		}
 | 
| -	}
 | 
| -	return m.buf, true
 | 
| -}
 | 
| -
 | 
| -func (c collatedProperties) indexEntries(k *datastore.Key, idxs []*qIndex) (*memStore, error) {
 | 
| -	m, err := c.indexableMap()
 | 
| -	if err != nil {
 | 
| -		return nil, err
 | 
| -	}
 | 
| -
 | 
| -	ret := newMemStore()
 | 
| -	idxColl := ret.SetCollection("idx", nil)
 | 
| -	// getIdxEnts retrieves an index collection or adds it if it's not there.
 | 
| -	getIdxEnts := func(qi *qIndex) *memCollection {
 | 
| -		buf := &bytes.Buffer{}
 | 
| -		qi.WriteBinary(buf)
 | 
| -		b := buf.Bytes()
 | 
| -		idxColl.Set(b, []byte{})
 | 
| -		return ret.SetCollection(fmt.Sprintf("idx:%s:%s", k.Namespace(), b), nil)
 | 
| -	}
 | 
| -
 | 
| -	buf := &bytes.Buffer{}
 | 
| -	writeKey(buf, noNS, k) // ns is in idxEnts collection name.
 | 
| -	keyData := buf.Bytes()
 | 
| -
 | 
| -	walkPermutations := func(prefix []byte, irg indexRowGen, ents *memCollection) {
 | 
| -		prev := []byte{} // intentionally make a non-nil slice, gkvlite hates nil.
 | 
| -		irg.permute(func(data []byte) {
 | 
| -			buf := bytes.NewBuffer(make([]byte, 0, len(prefix)+len(data)+len(keyData)))
 | 
| -			buf.Write(prefix)
 | 
| -			buf.Write(data)
 | 
| -			buf.Write(keyData)
 | 
| -			ents.Set(buf.Bytes(), prev)
 | 
| -			prev = data
 | 
| -		})
 | 
| -	}
 | 
| -
 | 
| -	mtch := matcher{}
 | 
| -	for _, idx := range idxs {
 | 
| -		if irg, ok := mtch.match(idx, m); ok {
 | 
| -			idxEnts := getIdxEnts(idx)
 | 
| -			if len(irg.propVec) == 0 {
 | 
| -				idxEnts.Set(keyData, []byte{}) // propless index, e.g. kind -> key = nil
 | 
| -			} else if idx.ancestor {
 | 
| -				for ancKey := k; ancKey != nil; ancKey = ancKey.Parent() {
 | 
| -					buf := &bytes.Buffer{}
 | 
| -					writeKey(buf, noNS, ancKey)
 | 
| -					walkPermutations(buf.Bytes(), irg, idxEnts)
 | 
| -				}
 | 
| -			} else {
 | 
| -				walkPermutations(nil, irg, idxEnts)
 | 
| -			}
 | 
| -		}
 | 
| -	}
 | 
| -
 | 
| -	return ret, nil
 | 
| -}
 | 
| -
 | 
| -func (pl *propertyList) indexEntriesWithBuiltins(k *datastore.Key, complexIdxs []*qIndex) (ret *memStore, err error) {
 | 
| -	c, err := pl.collate()
 | 
| -	if err == nil {
 | 
| -		ret, err = c.indexEntries(k, append(c.defaultIndicies(k.Kind()), complexIdxs...))
 | 
| -	}
 | 
| -	return
 | 
| -}
 | 
| -
 | 
| -func (pl *propertyList) collate() (collatedProperties, error) {
 | 
| -	if pl == nil || len(*pl) == 0 {
 | 
| -		return nil, nil
 | 
| -	}
 | 
| -
 | 
| -	cols := []*pvals{}
 | 
| -	colIdx := map[string]int{}
 | 
| -
 | 
| -	for _, p := range *pl {
 | 
| -		if idx, ok := colIdx[p.Name]; ok {
 | 
| -			c := cols[idx]
 | 
| -			td, err := newTypData(p.NoIndex, p.Value)
 | 
| -			if err != nil {
 | 
| -				return nil, err
 | 
| -			}
 | 
| -			c.vals = append(c.vals, td)
 | 
| -		} else {
 | 
| -			colIdx[p.Name] = len(cols)
 | 
| -			td, err := newTypData(p.NoIndex, p.Value)
 | 
| -			if err != nil {
 | 
| -				return nil, err
 | 
| -			}
 | 
| -			cols = append(cols, &pvals{p.Name, []*typData{td}})
 | 
| -		}
 | 
| -	}
 | 
| -
 | 
| -	return cols, nil
 | 
| -}
 | 
| -
 | 
| -func (pl *propertyList) addCollated(pv *pvals) {
 | 
| -	for _, v := range pv.vals {
 | 
| -		*pl = append(*pl, datastore.Property{
 | 
| -			Name:     pv.name,
 | 
| -			Multiple: len(pv.vals) > 1,
 | 
| -			NoIndex:  v.noIndex,
 | 
| -			Value:    v.data,
 | 
| -		})
 | 
| -	}
 | 
| -}
 | 
| -
 | 
| -func updateIndicies(store *memStore, key *datastore.Key, oldEnt, newEnt *propertyList) error {
 | 
| -	var err error
 | 
| -
 | 
| -	idxColl := store.GetCollection("idx")
 | 
| -	if idxColl == nil {
 | 
| -		idxColl = store.SetCollection("idx", nil)
 | 
| -	}
 | 
| -
 | 
| -	// load all current complex query index definitions.
 | 
| -	compIdx := []*qIndex{}
 | 
| -	idxColl.VisitItemsAscend(complexQueryPrefix, false, func(i *gkvlite.Item) bool {
 | 
| -		if !bytes.HasPrefix(i.Key, complexQueryPrefix) {
 | 
| -			return false
 | 
| -		}
 | 
| -		qi := &qIndex{}
 | 
| -		if err = qi.ReadBinary(bytes.NewBuffer(i.Key)); err != nil {
 | 
| -			return false
 | 
| -		}
 | 
| -		compIdx = append(compIdx, qi)
 | 
| -		return true
 | 
| -	})
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -
 | 
| -	oldIdx, err := oldEnt.indexEntriesWithBuiltins(key, compIdx)
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -
 | 
| -	newIdx, err := newEnt.indexEntriesWithBuiltins(key, compIdx)
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -
 | 
| -	prefix := "idx:" + key.Namespace() + ":"
 | 
| -
 | 
| -	gkvCollide(oldIdx.GetCollection("idx"), newIdx.GetCollection("idx"), func(k, ov, nv []byte) {
 | 
| -		ks := prefix + string(k)
 | 
| -		idxColl.Set(k, []byte{})
 | 
| -
 | 
| -		coll := store.GetCollection(ks)
 | 
| -		if coll == nil {
 | 
| -			coll = store.SetCollection(ks, nil)
 | 
| -		}
 | 
| -		oldColl := oldIdx.GetCollection(ks)
 | 
| -		newColl := newIdx.GetCollection(ks)
 | 
| -
 | 
| -		switch {
 | 
| -		case ov == nil && nv != nil: // all additions
 | 
| -			newColl.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool {
 | 
| -				coll.Set(i.Key, i.Val)
 | 
| -				return true
 | 
| -			})
 | 
| -		case ov != nil && nv == nil: // all deletions
 | 
| -			oldColl.VisitItemsAscend(nil, false, func(i *gkvlite.Item) bool {
 | 
| -				coll.Delete(i.Key)
 | 
| -				return true
 | 
| -			})
 | 
| -		case ov != nil && nv != nil: // merge
 | 
| -			gkvCollide(oldColl, newColl, func(k, ov, nv []byte) {
 | 
| -				if nv == nil {
 | 
| -					coll.Delete(k)
 | 
| -				} else {
 | 
| -					coll.Set(k, nv)
 | 
| -				}
 | 
| -			})
 | 
| -		default:
 | 
| -			panic("impossible")
 | 
| -		}
 | 
| -		// TODO(riannucci): remove entries from idxColl and remove index collections
 | 
| -		// when there are no index entries for that index any more.
 | 
| -	})
 | 
| -
 | 
| -	return nil
 | 
| -}
 | 
| -
 | 
| -func (pl *propertyList) MarshalBinary() ([]byte, error) {
 | 
| -	cols, err := pl.collate()
 | 
| -	if err != nil || len(cols) == 0 {
 | 
| -		return nil, err
 | 
| -	}
 | 
| -
 | 
| -	pieces := make([][]byte, 0, len(*pl)*2+1)
 | 
| -	for _, pv := range cols {
 | 
| -		// TODO(riannucci): estimate buffer size better.
 | 
| -		buf := bytes.NewBuffer(make([]byte, 0, cmpbin.MaxIntLen64+len(pv.name)))
 | 
| -		writeString(buf, pv.name)
 | 
| -		err := pv.WriteBinary(buf)
 | 
| -		if err != nil {
 | 
| -			return nil, err
 | 
| -		}
 | 
| -		pieces = append(pieces, buf.Bytes())
 | 
| -	}
 | 
| -	return bytes.Join(pieces, nil), nil
 | 
| -}
 | 
| -
 | 
| -func (pl *propertyList) UnmarshalBinary(data []byte) error {
 | 
| -	buf := bytes.NewBuffer(data)
 | 
| -	for buf.Len() > 0 {
 | 
| -		name, err := readString(buf)
 | 
| -		if err != nil {
 | 
| -			return err
 | 
| -		}
 | 
| -
 | 
| -		pv := &pvals{name: name}
 | 
| -		err = pv.ReadBinary(buf)
 | 
| -		if err != nil {
 | 
| -			return err
 | 
| -		}
 | 
| -		pl.addCollated(pv)
 | 
| -	}
 | 
| -
 | 
| -	return nil
 | 
| -}
 | 
| -
 | 
| -func toPL(src interface{}) (ret *propertyList, err error) {
 | 
| -	propchan := make(chan datastore.Property)
 | 
| -	ret = &propertyList{}
 | 
| -	go func() { err = datastore.SaveStruct(src, propchan) }()
 | 
| -	err2 := ret.Load(propchan)
 | 
| -	if err != nil {
 | 
| -		return
 | 
| -	}
 | 
| -	return ret, err2
 | 
| -}
 | 
| -
 | 
| -func fromPL(props *propertyList, dst interface{}) (err error) {
 | 
| -	propchan := make(chan datastore.Property)
 | 
| -	go func() { err = props.Save(propchan) }()
 | 
| -	err2 := datastore.LoadStruct(dst, propchan)
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -	return err2
 | 
| -}
 | 
| -
 | 
| -type propValType byte
 | 
| -
 | 
| -var byteSliceType = reflect.TypeOf([]byte(nil))
 | 
| -
 | 
| -// These constants are in the order described by
 | 
| -//   https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type_ordering
 | 
| -// with a slight divergence for the Int/Time split.
 | 
| -// NOTE: this enum can only occupy 7 bits, because we use the high bit to encode
 | 
| -// indexed/non-indexed. See typData.WriteBinary.
 | 
| -const (
 | 
| -	pvNull propValType = iota
 | 
| -	pvInt
 | 
| -
 | 
| -	// NOTE: this is a slight divergence; times and integers actually sort
 | 
| -	// together (apparently?) in datastore. This is probably insane, and I don't
 | 
| -	// want to add the complexity of field 'meaning' as a sparate concept from the
 | 
| -	// field's 'type' (which is what datastore seems to do, judging from the
 | 
| -	// protobufs). So if you're here because you implemented an app which relies
 | 
| -	// on time.Time and int64 sorting together, then this is why your app acts
 | 
| -	// differently in production. My advice is to NOT DO THAT. If you really want
 | 
| -	// this (and you probably don't), you should take care of the time.Time <->
 | 
| -	// int64 conversion in your app and just use a property type of int64.
 | 
| -	pvTime
 | 
| -
 | 
| -	// NOTE: this is also a slight divergence, but not a semantic one. IIUC, in
 | 
| -	// datastore 'bool' is actually the type and the value is either 0 or
 | 
| -	// 1 (taking another byte to store). Since we have plenty of space in this
 | 
| -	// type byte, I just merge the value into the type for booleans. If this
 | 
| -	// becomes problematic, consider changing this to just pvBool, and then
 | 
| -	// encoding a 0 or 1 as a byte in the relevant marshalling routines.
 | 
| -	pvBoolFalse
 | 
| -	pvBoolTrue
 | 
| -	pvBytes // []byte or datastore.ByteString
 | 
| -	pvStr   // string or string noindex
 | 
| -	pvFloat
 | 
| -	pvGeoPoint
 | 
| -
 | 
| -	// These two are problematic, because they force us to bind to the appengine
 | 
| -	// SDK code. If we can drop support for these and turn them into hard errors,
 | 
| -	// that could let us decouple from the various appengine SDKs. Maybe.
 | 
| -	pvKey     // TODO(riannucci): remove support for this  (use a string)
 | 
| -	pvBlobKey // TODO(riannucci): remove support for this  (use a string)
 | 
| -
 | 
| -	pvUNKNOWN
 | 
| -)
 | 
| -
 | 
| -func (p *pvals) ReadBinary(buf *bytes.Buffer) error {
 | 
| -	n, _, err := cmpbin.ReadUint(buf)
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -
 | 
| -	p.vals = make([]*typData, n)
 | 
| -	for i := range p.vals {
 | 
| -		p.vals[i] = &typData{}
 | 
| -		err := p.vals[i].ReadBinary(buf, withNS, "")
 | 
| -		if err != nil {
 | 
| -			return err
 | 
| -		}
 | 
| -	}
 | 
| -
 | 
| -	return nil
 | 
| -}
 | 
| -
 | 
| -func (p *pvals) WriteBinary(buf *bytes.Buffer) error {
 | 
| -	cmpbin.WriteUint(buf, uint64(len(p.vals)))
 | 
| -	for _, v := range p.vals {
 | 
| -		if err := v.WriteBinary(buf, withNS); err != nil {
 | 
| -			return err
 | 
| -		}
 | 
| -	}
 | 
| -	return nil
 | 
| -}
 | 
| 
 |