| Index: service/datastore/datastore.go
 | 
| diff --git a/service/datastore/datastore.go b/service/datastore/datastore.go
 | 
| index 5acacbd02b643a13e22b43d6b81ec856e1836909..99f7f40953a1defdca618db13fc0a3fbad5e752e 100644
 | 
| --- a/service/datastore/datastore.go
 | 
| +++ b/service/datastore/datastore.go
 | 
| @@ -10,542 +10,12 @@ import (
 | 
|  	"io/ioutil"
 | 
|  	"os"
 | 
|  	"path/filepath"
 | 
| -	"reflect"
 | 
|  	"runtime"
 | 
|  	"strings"
 | 
|  
 | 
| -	"github.com/luci/luci-go/common/errors"
 | 
| -
 | 
|  	"gopkg.in/yaml.v2"
 | 
|  )
 | 
|  
 | 
| -type datastoreImpl struct {
 | 
| -	RawInterface
 | 
| -
 | 
| -	aid string
 | 
| -	ns  string
 | 
| -}
 | 
| -
 | 
| -var _ Interface = (*datastoreImpl)(nil)
 | 
| -
 | 
| -func (d *datastoreImpl) KeyForObj(src interface{}) *Key {
 | 
| -	ret, err := d.KeyForObjErr(src)
 | 
| -	if err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -	return ret
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) KeyForObjErr(src interface{}) (*Key, error) {
 | 
| -	return newKeyObjErr(d.aid, d.ns, getMGS(src))
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) MakeKey(elems ...interface{}) *Key {
 | 
| -	return MakeKey(d.aid, d.ns, elems...)
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) NewKey(kind, stringID string, intID int64, parent *Key) *Key {
 | 
| -	return NewKey(d.aid, d.ns, kind, stringID, intID, parent)
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) NewIncompleteKeys(count int, kind string, parent *Key) (keys []*Key) {
 | 
| -	if count > 0 {
 | 
| -		keys = make([]*Key, count)
 | 
| -		for i := range keys {
 | 
| -			keys[i] = d.NewKey(kind, "", 0, parent)
 | 
| -		}
 | 
| -	}
 | 
| -	return
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) NewKeyToks(toks []KeyTok) *Key {
 | 
| -	return NewKeyToks(d.aid, d.ns, toks)
 | 
| -}
 | 
| -
 | 
| -// PopulateKey loads key into obj.
 | 
| -//
 | 
| -// obj is any object that Interface.Get is able to accept.
 | 
| -//
 | 
| -// Upon successful application, this method will return true. If the key could
 | 
| -// not be applied to the object, this method will return false. It will panic if
 | 
| -// obj is an invalid datastore model.
 | 
| -//
 | 
| -// This method will panic if obj is an invalid datastore model. If the key could
 | 
| -// not be applied to the object, nothing will happen.
 | 
| -func PopulateKey(obj interface{}, key *Key) bool {
 | 
| -	return populateKeyMGS(getMGS(obj), key)
 | 
| -}
 | 
| -
 | 
| -func populateKeyMGS(mgs MetaGetterSetter, key *Key) bool {
 | 
| -	if mgs.SetMeta("key", key) {
 | 
| -		return true
 | 
| -	}
 | 
| -
 | 
| -	lst := key.LastTok()
 | 
| -	if lst.StringID != "" {
 | 
| -		if !mgs.SetMeta("id", lst.StringID) {
 | 
| -			return false
 | 
| -		}
 | 
| -	} else {
 | 
| -		if !mgs.SetMeta("id", lst.IntID) {
 | 
| -			return false
 | 
| -		}
 | 
| -	}
 | 
| -
 | 
| -	mgs.SetMeta("kind", lst.Kind)
 | 
| -	mgs.SetMeta("parent", key.Parent())
 | 
| -	return true
 | 
| -}
 | 
| -
 | 
| -func checkMultiSliceType(v interface{}) error {
 | 
| -	if reflect.TypeOf(v).Kind() == reflect.Slice {
 | 
| -		return nil
 | 
| -	}
 | 
| -	return fmt.Errorf("argument must be a slice, not %T", v)
 | 
| -
 | 
| -}
 | 
| -
 | 
| -func runParseCallback(cbIface interface{}) (isKey, hasErr, hasCursorCB bool, mat *multiArgType) {
 | 
| -	badSig := func() {
 | 
| -		panic(fmt.Errorf(
 | 
| -			"cb does not match the required callback signature: `%T` != `func(TYPE, [CursorCB]) [error]`",
 | 
| -			cbIface))
 | 
| -	}
 | 
| -
 | 
| -	if cbIface == nil {
 | 
| -		badSig()
 | 
| -	}
 | 
| -
 | 
| -	// TODO(riannucci): Profile and determine if any of this is causing a real
 | 
| -	// slowdown. Could potentially cache reflection stuff by cbTyp?
 | 
| -	cbTyp := reflect.TypeOf(cbIface)
 | 
| -
 | 
| -	if cbTyp.Kind() != reflect.Func {
 | 
| -		badSig()
 | 
| -	}
 | 
| -
 | 
| -	numIn := cbTyp.NumIn()
 | 
| -	if numIn != 1 && numIn != 2 {
 | 
| -		badSig()
 | 
| -	}
 | 
| -
 | 
| -	firstArg := cbTyp.In(0)
 | 
| -	if firstArg == typeOfKey {
 | 
| -		isKey = true
 | 
| -	} else {
 | 
| -		mat = mustParseArg(firstArg, false)
 | 
| -		if mat.newElem == nil {
 | 
| -			badSig()
 | 
| -		}
 | 
| -	}
 | 
| -
 | 
| -	hasCursorCB = numIn == 2
 | 
| -	if hasCursorCB && cbTyp.In(1) != typeOfCursorCB {
 | 
| -		badSig()
 | 
| -	}
 | 
| -
 | 
| -	if cbTyp.NumOut() > 1 {
 | 
| -		badSig()
 | 
| -	} else if cbTyp.NumOut() == 1 && cbTyp.Out(0) != typeOfError {
 | 
| -		badSig()
 | 
| -	}
 | 
| -	hasErr = cbTyp.NumOut() == 1
 | 
| -
 | 
| -	return
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) AllocateIDs(ent ...interface{}) error {
 | 
| -	if len(ent) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	mma, err := makeMetaMultiArg(ent, mmaWriteKeys)
 | 
| -	if err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -
 | 
| -	keys, _, err := mma.getKeysPMs(d.aid, d.ns, false)
 | 
| -	if err != nil {
 | 
| -		if len(ent) == 1 {
 | 
| -			// Single-argument Exists will return a single error.
 | 
| -			err = errors.SingleError(err)
 | 
| -		}
 | 
| -		return err
 | 
| -	}
 | 
| -	if len(keys) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	// Convert each key to be partial valid, assigning an integer ID of 0. Confirm
 | 
| -	// that each object can be populated with such a key.
 | 
| -	for i, key := range keys {
 | 
| -		keys[i] = key.Incomplete()
 | 
| -	}
 | 
| -
 | 
| -	var et errorTracker
 | 
| -	it := mma.iterator(et.init(mma))
 | 
| -	err = filterStop(d.RawInterface.AllocateIDs(keys, func(key *Key, err error) error {
 | 
| -		it.next(func(mat *multiArgType, v reflect.Value) error {
 | 
| -			if err != nil {
 | 
| -				return err
 | 
| -			}
 | 
| -
 | 
| -			if !mat.setKey(v, key) {
 | 
| -				return ErrInvalidKey
 | 
| -			}
 | 
| -			return nil
 | 
| -		})
 | 
| -
 | 
| -		return nil
 | 
| -	}))
 | 
| -	if err == nil {
 | 
| -		err = et.error()
 | 
| -	}
 | 
| -
 | 
| -	if err != nil && len(ent) == 1 {
 | 
| -		// Single-argument Exists will return a single error.
 | 
| -		err = errors.SingleError(err)
 | 
| -	}
 | 
| -	return err
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) Run(q *Query, cbIface interface{}) error {
 | 
| -	isKey, hasErr, hasCursorCB, mat := runParseCallback(cbIface)
 | 
| -
 | 
| -	if isKey {
 | 
| -		q = q.KeysOnly(true)
 | 
| -	}
 | 
| -	fq, err := q.Finalize()
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -
 | 
| -	cbVal := reflect.ValueOf(cbIface)
 | 
| -	var cb func(reflect.Value, CursorCB) error
 | 
| -	switch {
 | 
| -	case hasErr && hasCursorCB:
 | 
| -		cb = func(v reflect.Value, cb CursorCB) error {
 | 
| -			err := cbVal.Call([]reflect.Value{v, reflect.ValueOf(cb)})[0].Interface()
 | 
| -			if err != nil {
 | 
| -				return err.(error)
 | 
| -			}
 | 
| -			return nil
 | 
| -		}
 | 
| -
 | 
| -	case hasErr && !hasCursorCB:
 | 
| -		cb = func(v reflect.Value, _ CursorCB) error {
 | 
| -			err := cbVal.Call([]reflect.Value{v})[0].Interface()
 | 
| -			if err != nil {
 | 
| -				return err.(error)
 | 
| -			}
 | 
| -			return nil
 | 
| -		}
 | 
| -
 | 
| -	case !hasErr && hasCursorCB:
 | 
| -		cb = func(v reflect.Value, cb CursorCB) error {
 | 
| -			cbVal.Call([]reflect.Value{v, reflect.ValueOf(cb)})
 | 
| -			return nil
 | 
| -		}
 | 
| -
 | 
| -	case !hasErr && !hasCursorCB:
 | 
| -		cb = func(v reflect.Value, _ CursorCB) error {
 | 
| -			cbVal.Call([]reflect.Value{v})
 | 
| -			return nil
 | 
| -		}
 | 
| -	}
 | 
| -
 | 
| -	if isKey {
 | 
| -		err = d.RawInterface.Run(fq, func(k *Key, _ PropertyMap, gc CursorCB) error {
 | 
| -			return cb(reflect.ValueOf(k), gc)
 | 
| -		})
 | 
| -	} else {
 | 
| -		err = d.RawInterface.Run(fq, func(k *Key, pm PropertyMap, gc CursorCB) error {
 | 
| -			itm := mat.newElem()
 | 
| -			if err := mat.setPM(itm, pm); err != nil {
 | 
| -				return err
 | 
| -			}
 | 
| -			mat.setKey(itm, k)
 | 
| -			return cb(itm, gc)
 | 
| -		})
 | 
| -	}
 | 
| -	return filterStop(err)
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) Count(q *Query) (int64, error) {
 | 
| -	fq, err := q.Finalize()
 | 
| -	if err != nil {
 | 
| -		return 0, err
 | 
| -	}
 | 
| -	v, err := d.RawInterface.Count(fq)
 | 
| -	return v, filterStop(err)
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) GetAll(q *Query, dst interface{}) error {
 | 
| -	v := reflect.ValueOf(dst)
 | 
| -	if v.Kind() != reflect.Ptr {
 | 
| -		panic(fmt.Errorf("invalid GetAll dst: must have a ptr-to-slice: %T", dst))
 | 
| -	}
 | 
| -	if !v.IsValid() || v.IsNil() {
 | 
| -		panic(errors.New("invalid GetAll dst: <nil>"))
 | 
| -	}
 | 
| -
 | 
| -	if keys, ok := dst.(*[]*Key); ok {
 | 
| -		fq, err := q.KeysOnly(true).Finalize()
 | 
| -		if err != nil {
 | 
| -			return err
 | 
| -		}
 | 
| -
 | 
| -		return d.RawInterface.Run(fq, func(k *Key, _ PropertyMap, _ CursorCB) error {
 | 
| -			*keys = append(*keys, k)
 | 
| -			return nil
 | 
| -		})
 | 
| -	}
 | 
| -	fq, err := q.Finalize()
 | 
| -	if err != nil {
 | 
| -		return err
 | 
| -	}
 | 
| -
 | 
| -	slice := v.Elem()
 | 
| -	mat := mustParseMultiArg(slice.Type())
 | 
| -	if mat.newElem == nil {
 | 
| -		panic(fmt.Errorf("invalid GetAll dst (non-concrete element type): %T", dst))
 | 
| -	}
 | 
| -
 | 
| -	errs := map[int]error{}
 | 
| -	i := 0
 | 
| -	err = filterStop(d.RawInterface.Run(fq, func(k *Key, pm PropertyMap, _ CursorCB) error {
 | 
| -		slice.Set(reflect.Append(slice, mat.newElem()))
 | 
| -		itm := slice.Index(i)
 | 
| -		mat.setKey(itm, k)
 | 
| -		err := mat.setPM(itm, pm)
 | 
| -		if err != nil {
 | 
| -			errs[i] = err
 | 
| -		}
 | 
| -		i++
 | 
| -		return nil
 | 
| -	}))
 | 
| -	if err == nil {
 | 
| -		if len(errs) > 0 {
 | 
| -			me := make(errors.MultiError, slice.Len())
 | 
| -			for i, e := range errs {
 | 
| -				me[i] = e
 | 
| -			}
 | 
| -			err = me
 | 
| -		}
 | 
| -	}
 | 
| -	return err
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) Exists(ent ...interface{}) (*ExistsResult, error) {
 | 
| -	if len(ent) == 0 {
 | 
| -		return nil, nil
 | 
| -	}
 | 
| -
 | 
| -	mma, err := makeMetaMultiArg(ent, mmaKeysOnly)
 | 
| -	if err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -
 | 
| -	keys, _, err := mma.getKeysPMs(d.aid, d.ns, false)
 | 
| -	if err != nil {
 | 
| -		if len(ent) == 1 {
 | 
| -			// Single-argument Exists will return a single error.
 | 
| -			err = errors.SingleError(err)
 | 
| -		}
 | 
| -		return nil, err
 | 
| -	}
 | 
| -	if len(keys) == 0 {
 | 
| -		return nil, nil
 | 
| -	}
 | 
| -
 | 
| -	var bt boolTracker
 | 
| -	it := mma.iterator(bt.init(mma))
 | 
| -	err = filterStop(d.RawInterface.GetMulti(keys, nil, func(_ PropertyMap, err error) error {
 | 
| -		it.next(func(*multiArgType, reflect.Value) error {
 | 
| -			return err
 | 
| -		})
 | 
| -		return nil
 | 
| -	}))
 | 
| -	if err == nil {
 | 
| -		err = bt.error()
 | 
| -	}
 | 
| -
 | 
| -	if err != nil && len(ent) == 1 {
 | 
| -		// Single-argument Exists will return a single error.
 | 
| -		err = errors.SingleError(err)
 | 
| -	}
 | 
| -	return bt.result(), err
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) ExistsMulti(keys []*Key) (BoolList, error) {
 | 
| -	v, err := d.Exists(keys)
 | 
| -	if err != nil {
 | 
| -		return nil, err
 | 
| -	}
 | 
| -	return v.List(0), nil
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) Get(dst ...interface{}) (err error) {
 | 
| -	if len(dst) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	mma, err := makeMetaMultiArg(dst, mmaReadWrite)
 | 
| -	if err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -
 | 
| -	keys, pms, err := mma.getKeysPMs(d.aid, d.ns, true)
 | 
| -	if err != nil {
 | 
| -		if len(dst) == 1 {
 | 
| -			// Single-argument Get will return a single error.
 | 
| -			err = errors.SingleError(err)
 | 
| -		}
 | 
| -		return err
 | 
| -	}
 | 
| -	if len(keys) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	var et errorTracker
 | 
| -	it := mma.iterator(et.init(mma))
 | 
| -	meta := NewMultiMetaGetter(pms)
 | 
| -	err = filterStop(d.RawInterface.GetMulti(keys, meta, func(pm PropertyMap, err error) error {
 | 
| -		it.next(func(mat *multiArgType, slot reflect.Value) error {
 | 
| -			if err != nil {
 | 
| -				return err
 | 
| -			}
 | 
| -			return mat.setPM(slot, pm)
 | 
| -		})
 | 
| -		return nil
 | 
| -	}))
 | 
| -
 | 
| -	if err == nil {
 | 
| -		err = et.error()
 | 
| -	}
 | 
| -
 | 
| -	if err != nil && len(dst) == 1 {
 | 
| -		// Single-argument Get will return a single error.
 | 
| -		err = errors.SingleError(err)
 | 
| -	}
 | 
| -	return err
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) GetMulti(dst interface{}) error {
 | 
| -	if err := checkMultiSliceType(dst); err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -	return d.Get(dst)
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) Put(src ...interface{}) (err error) {
 | 
| -	if len(src) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	mma, err := makeMetaMultiArg(src, mmaReadWrite)
 | 
| -	if err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -
 | 
| -	keys, vals, err := mma.getKeysPMs(d.aid, d.ns, false)
 | 
| -	if err != nil {
 | 
| -		if len(src) == 1 {
 | 
| -			// Single-argument Put will return a single error.
 | 
| -			err = errors.SingleError(err)
 | 
| -		}
 | 
| -		return err
 | 
| -	}
 | 
| -	if len(keys) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	i := 0
 | 
| -	var et errorTracker
 | 
| -	it := mma.iterator(et.init(mma))
 | 
| -	err = filterStop(d.RawInterface.PutMulti(keys, vals, func(key *Key, err error) error {
 | 
| -		it.next(func(mat *multiArgType, slot reflect.Value) error {
 | 
| -			if err != nil {
 | 
| -				return err
 | 
| -			}
 | 
| -			if key != keys[i] {
 | 
| -				mat.setKey(slot, key)
 | 
| -			}
 | 
| -			return nil
 | 
| -		})
 | 
| -
 | 
| -		i++
 | 
| -		return nil
 | 
| -	}))
 | 
| -
 | 
| -	if err == nil {
 | 
| -		err = et.error()
 | 
| -	}
 | 
| -
 | 
| -	if err != nil && len(src) == 1 {
 | 
| -		// Single-argument Put will return a single error.
 | 
| -		err = errors.SingleError(err)
 | 
| -	}
 | 
| -	return err
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) PutMulti(src interface{}) error {
 | 
| -	if err := checkMultiSliceType(src); err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -	return d.Put(src)
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) Delete(ent ...interface{}) error {
 | 
| -	if len(ent) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	mma, err := makeMetaMultiArg(ent, mmaKeysOnly)
 | 
| -	if err != nil {
 | 
| -		panic(err)
 | 
| -	}
 | 
| -
 | 
| -	keys, _, err := mma.getKeysPMs(d.aid, d.ns, false)
 | 
| -	if err != nil {
 | 
| -		if len(ent) == 1 {
 | 
| -			// Single-argument Delete will return a single error.
 | 
| -			err = errors.SingleError(err)
 | 
| -		}
 | 
| -		return err
 | 
| -	}
 | 
| -	if len(keys) == 0 {
 | 
| -		return nil
 | 
| -	}
 | 
| -
 | 
| -	var et errorTracker
 | 
| -	it := mma.iterator(et.init(mma))
 | 
| -	err = filterStop(d.RawInterface.DeleteMulti(keys, func(err error) error {
 | 
| -		it.next(func(*multiArgType, reflect.Value) error {
 | 
| -			return err
 | 
| -		})
 | 
| -
 | 
| -		return nil
 | 
| -	}))
 | 
| -	if err == nil {
 | 
| -		err = et.error()
 | 
| -	}
 | 
| -
 | 
| -	if err != nil && len(ent) == 1 {
 | 
| -		// Single-argument Delete will return a single error.
 | 
| -		err = errors.SingleError(err)
 | 
| -	}
 | 
| -	return err
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) DeleteMulti(keys []*Key) error {
 | 
| -	return d.Delete(keys)
 | 
| -}
 | 
| -
 | 
| -func (d *datastoreImpl) Raw() RawInterface {
 | 
| -	return d.RawInterface
 | 
| -}
 | 
| -
 | 
|  // ParseIndexYAML parses the contents of a index YAML file into a list of
 | 
|  // IndexDefinitions.
 | 
|  func ParseIndexYAML(content io.Reader) ([]*IndexDefinition, error) {
 | 
| @@ -628,10 +98,3 @@ func FindAndParseIndexYAML(path string) ([]*IndexDefinition, error) {
 | 
|  		currentDir = filepath.Dir(currentDir)
 | 
|  	}
 | 
|  }
 | 
| -
 | 
| -func filterStop(err error) error {
 | 
| -	if err == Stop {
 | 
| -		err = nil
 | 
| -	}
 | 
| -	return err
 | 
| -}
 | 
| 
 |