| Index: go/src/infra/gae/libs/gae/helper/datastore_impl.go
 | 
| diff --git a/go/src/infra/gae/libs/gae/helper/datastore_impl.go b/go/src/infra/gae/libs/gae/helper/datastore_impl.go
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..bfe066ea4871b9713e6ade287f3a284fe316d5e7
 | 
| --- /dev/null
 | 
| +++ b/go/src/infra/gae/libs/gae/helper/datastore_impl.go
 | 
| @@ -0,0 +1,509 @@
 | 
| +// 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.
 | 
| +
 | 
| +// HEAVILY adapted from github.com/golang/appengine/datastore
 | 
| +
 | 
| +package helper
 | 
| +
 | 
| +import (
 | 
| +	"errors"
 | 
| +	"fmt"
 | 
| +	"infra/gae/libs/gae"
 | 
| +	"reflect"
 | 
| +	"strings"
 | 
| +	"sync"
 | 
| +	"time"
 | 
| +	"unicode"
 | 
| +)
 | 
| +
 | 
| +// Entities with more than this many indexed properties will not be saved.
 | 
| +const maxIndexedProperties = 20000
 | 
| +
 | 
| +var (
 | 
| +	typeOfDSKey               = reflect.TypeOf((*gae.DSKey)(nil)).Elem()
 | 
| +	typeOfDSPropertyConverter = reflect.TypeOf((*gae.DSPropertyConverter)(nil)).Elem()
 | 
| +	typeOfGeoPoint            = reflect.TypeOf(gae.DSGeoPoint{})
 | 
| +	typeOfTime                = reflect.TypeOf(time.Time{})
 | 
| +
 | 
| +	valueOfnilDSKey = reflect.Zero(typeOfDSKey)
 | 
| +)
 | 
| +
 | 
| +type structTag struct {
 | 
| +	name           string
 | 
| +	noIndex        bool
 | 
| +	isSlice        bool
 | 
| +	substructCodec *structCodec
 | 
| +	convert        bool
 | 
| +	specialVal     string
 | 
| +}
 | 
| +
 | 
| +type structCodec struct {
 | 
| +	bySpecial map[string]int
 | 
| +	byName    map[string]int
 | 
| +	byIndex   []structTag
 | 
| +	hasSlice  bool
 | 
| +	problem   error
 | 
| +}
 | 
| +
 | 
| +type structPLS struct {
 | 
| +	o reflect.Value
 | 
| +	c *structCodec
 | 
| +}
 | 
| +
 | 
| +var _ gae.DSStructPLS = (*structPLS)(nil)
 | 
| +
 | 
| +// typeMismatchReason returns a string explaining why the property p could not
 | 
| +// be stored in an entity field of type v.Type().
 | 
| +func typeMismatchReason(val interface{}, v reflect.Value) string {
 | 
| +	entityType := reflect.TypeOf(val)
 | 
| +	return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type())
 | 
| +}
 | 
| +
 | 
| +func (p *structPLS) Load(propMap gae.DSPropertyMap) (convFailures []string, fatal error) {
 | 
| +	if fatal = p.Problem(); fatal != nil {
 | 
| +		return
 | 
| +	}
 | 
| +
 | 
| +	t := p.o.Type()
 | 
| +	for name, props := range propMap {
 | 
| +		multiple := len(props) > 1
 | 
| +		for i, prop := range props {
 | 
| +			if reason := loadInner(p.c, p.o, i, name, prop, multiple); reason != "" {
 | 
| +				convFailures = append(convFailures, fmt.Sprintf(
 | 
| +					"cannot load field %q into a %q: %s", name, t, reason))
 | 
| +			}
 | 
| +		}
 | 
| +	}
 | 
| +	return
 | 
| +}
 | 
| +
 | 
| +func loadInner(codec *structCodec, structValue reflect.Value, index int, name string, p gae.DSProperty, requireSlice bool) string {
 | 
| +	var v reflect.Value
 | 
| +	// Traverse a struct's struct-typed fields.
 | 
| +	for {
 | 
| +		fieldIndex, ok := codec.byName[name]
 | 
| +		if !ok {
 | 
| +			return "no such struct field"
 | 
| +		}
 | 
| +		v = structValue.Field(fieldIndex)
 | 
| +
 | 
| +		st := codec.byIndex[fieldIndex]
 | 
| +		if st.substructCodec == nil {
 | 
| +			break
 | 
| +		}
 | 
| +
 | 
| +		if v.Kind() == reflect.Slice {
 | 
| +			for v.Len() <= index {
 | 
| +				v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem()))
 | 
| +			}
 | 
| +			structValue = v.Index(index)
 | 
| +			requireSlice = false
 | 
| +		} else {
 | 
| +			structValue = v
 | 
| +		}
 | 
| +		// Strip the "I." from "I.X".
 | 
| +		name = name[len(st.name):]
 | 
| +		codec = st.substructCodec
 | 
| +	}
 | 
| +
 | 
| +	doConversion := func(v reflect.Value) (string, bool) {
 | 
| +		a := v.Addr()
 | 
| +		if conv, ok := a.Interface().(gae.DSPropertyConverter); ok {
 | 
| +			err := conv.FromDSProperty(p)
 | 
| +			if err != nil {
 | 
| +				return err.Error(), true
 | 
| +			}
 | 
| +			return "", true
 | 
| +		}
 | 
| +		return "", false
 | 
| +	}
 | 
| +
 | 
| +	if ret, ok := doConversion(v); ok {
 | 
| +		return ret
 | 
| +	}
 | 
| +
 | 
| +	var slice reflect.Value
 | 
| +	if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
 | 
| +		slice = v
 | 
| +		v = reflect.New(v.Type().Elem()).Elem()
 | 
| +	} else if requireSlice {
 | 
| +		return "multiple-valued property requires a slice field type"
 | 
| +	}
 | 
| +
 | 
| +	pVal := p.Value()
 | 
| +
 | 
| +	if ret, ok := doConversion(v); ok {
 | 
| +		if ret != "" {
 | 
| +			return ret
 | 
| +		}
 | 
| +	} else {
 | 
| +		knd := v.Kind()
 | 
| +		if v.Type().Implements(typeOfDSKey) {
 | 
| +			knd = reflect.Interface
 | 
| +		}
 | 
| +		switch knd {
 | 
| +		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
| +			x, ok := pVal.(int64)
 | 
| +			if !ok && pVal != nil {
 | 
| +				return typeMismatchReason(pVal, v)
 | 
| +			}
 | 
| +			if v.OverflowInt(x) {
 | 
| +				return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
 | 
| +			}
 | 
| +			v.SetInt(x)
 | 
| +		case reflect.Bool:
 | 
| +			x, ok := pVal.(bool)
 | 
| +			if !ok && pVal != nil {
 | 
| +				return typeMismatchReason(pVal, v)
 | 
| +			}
 | 
| +			v.SetBool(x)
 | 
| +		case reflect.String:
 | 
| +			switch x := pVal.(type) {
 | 
| +			case gae.BSKey:
 | 
| +				v.SetString(string(x))
 | 
| +			case string:
 | 
| +				v.SetString(x)
 | 
| +			default:
 | 
| +				if pVal != nil {
 | 
| +					return typeMismatchReason(pVal, v)
 | 
| +				}
 | 
| +			}
 | 
| +		case reflect.Float32, reflect.Float64:
 | 
| +			x, ok := pVal.(float64)
 | 
| +			if !ok && pVal != nil {
 | 
| +				return typeMismatchReason(pVal, v)
 | 
| +			}
 | 
| +			if v.OverflowFloat(x) {
 | 
| +				return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
 | 
| +			}
 | 
| +			v.SetFloat(x)
 | 
| +		case reflect.Interface:
 | 
| +			x, ok := pVal.(gae.DSKey)
 | 
| +			if !ok && pVal != nil {
 | 
| +				return typeMismatchReason(pVal, v)
 | 
| +			}
 | 
| +			if x != nil {
 | 
| +				v.Set(reflect.ValueOf(x))
 | 
| +			}
 | 
| +		case reflect.Struct:
 | 
| +			switch v.Type() {
 | 
| +			case typeOfTime:
 | 
| +				x, ok := pVal.(time.Time)
 | 
| +				if !ok && pVal != nil {
 | 
| +					return typeMismatchReason(pVal, v)
 | 
| +				}
 | 
| +				v.Set(reflect.ValueOf(x))
 | 
| +			case typeOfGeoPoint:
 | 
| +				x, ok := pVal.(gae.DSGeoPoint)
 | 
| +				if !ok && pVal != nil {
 | 
| +					return typeMismatchReason(pVal, v)
 | 
| +				}
 | 
| +				v.Set(reflect.ValueOf(x))
 | 
| +			default:
 | 
| +				panic(fmt.Errorf("helper: impossible: %s", typeMismatchReason(pVal, v)))
 | 
| +			}
 | 
| +		case reflect.Slice:
 | 
| +			switch x := pVal.(type) {
 | 
| +			case []byte:
 | 
| +				v.SetBytes(x)
 | 
| +			case gae.DSByteString:
 | 
| +				v.SetBytes([]byte(x))
 | 
| +			default:
 | 
| +				panic(fmt.Errorf("helper: impossible: %s", typeMismatchReason(pVal, v)))
 | 
| +			}
 | 
| +		default:
 | 
| +			panic(fmt.Errorf("helper: impossible: %s", typeMismatchReason(pVal, v)))
 | 
| +		}
 | 
| +	}
 | 
| +	if slice.IsValid() {
 | 
| +		slice.Set(reflect.Append(slice, v))
 | 
| +	}
 | 
| +	return ""
 | 
| +}
 | 
| +
 | 
| +func (p *structPLS) Save() (gae.DSPropertyMap, error) {
 | 
| +	ret := gae.DSPropertyMap{}
 | 
| +	idxCount := 0
 | 
| +	if err := p.save(ret, &idxCount, "", false); err != nil {
 | 
| +		return nil, err
 | 
| +	}
 | 
| +	return ret, nil
 | 
| +}
 | 
| +
 | 
| +func (p *structPLS) save(propMap gae.DSPropertyMap, idxCount *int, prefix string, noIndex bool) (err error) {
 | 
| +	if err = p.Problem(); err != nil {
 | 
| +		return
 | 
| +	}
 | 
| +
 | 
| +	saveProp := func(name string, ni bool, v reflect.Value, st *structTag) (err error) {
 | 
| +		if st.substructCodec != nil {
 | 
| +			return (&structPLS{v, st.substructCodec}).save(propMap, idxCount, name, ni)
 | 
| +		}
 | 
| +
 | 
| +		prop := gae.DSProperty{}
 | 
| +		if st.convert {
 | 
| +			prop, err = v.Addr().Interface().(gae.DSPropertyConverter).ToDSProperty()
 | 
| +		} else {
 | 
| +			err = prop.SetValue(v.Interface(), ni)
 | 
| +		}
 | 
| +		if err != nil {
 | 
| +			return err
 | 
| +		}
 | 
| +		propMap[name] = append(propMap[name], prop)
 | 
| +		if !prop.NoIndex() {
 | 
| +			*idxCount++
 | 
| +			if *idxCount > maxIndexedProperties {
 | 
| +				return errors.New("gae: too many indexed properties")
 | 
| +			}
 | 
| +		}
 | 
| +		return nil
 | 
| +	}
 | 
| +
 | 
| +	for i, st := range p.c.byIndex {
 | 
| +		if st.name == "-" {
 | 
| +			continue
 | 
| +		}
 | 
| +		name := st.name
 | 
| +		if prefix != "" {
 | 
| +			name = prefix + name
 | 
| +		}
 | 
| +		v := p.o.Field(i)
 | 
| +		noIndex1 := noIndex || st.noIndex
 | 
| +		if st.isSlice {
 | 
| +			for j := 0; j < v.Len(); j++ {
 | 
| +				if err := saveProp(name, noIndex1, v.Index(j), &st); err != nil {
 | 
| +					return err
 | 
| +				}
 | 
| +			}
 | 
| +		} else {
 | 
| +			if err := saveProp(name, noIndex1, v, &st); err != nil {
 | 
| +				return err
 | 
| +			}
 | 
| +		}
 | 
| +	}
 | 
| +	return nil
 | 
| +}
 | 
| +
 | 
| +func (p *structPLS) GetSpecial(key string) (val string, current interface{}, err error) {
 | 
| +	if err = p.Problem(); err != nil {
 | 
| +		return
 | 
| +	}
 | 
| +	idx, ok := p.c.bySpecial[key]
 | 
| +	if !ok {
 | 
| +		err = gae.ErrDSSpecialFieldUnset
 | 
| +		return
 | 
| +	}
 | 
| +	val = p.c.byIndex[idx].specialVal
 | 
| +	f := p.o.Field(idx)
 | 
| +	if f.CanSet() {
 | 
| +		current = f.Interface()
 | 
| +	}
 | 
| +	return
 | 
| +}
 | 
| +
 | 
| +func (p *structPLS) SetSpecial(key string, val interface{}) (err error) {
 | 
| +	if err = p.Problem(); err != nil {
 | 
| +		return
 | 
| +	}
 | 
| +	idx, ok := p.c.bySpecial[key]
 | 
| +	if !ok {
 | 
| +		return gae.ErrDSSpecialFieldUnset
 | 
| +	}
 | 
| +	defer func() {
 | 
| +		pv := recover()
 | 
| +		if pv != nil && err == nil {
 | 
| +			err = fmt.Errorf("gae/helper: cannot set special %q: %s", key, pv)
 | 
| +		}
 | 
| +	}()
 | 
| +	p.o.Field(idx).Set(reflect.ValueOf(val))
 | 
| +	return nil
 | 
| +}
 | 
| +
 | 
| +func (p *structPLS) Problem() error { return p.c.problem }
 | 
| +
 | 
| +var (
 | 
| +	// The RWMutex is chosen intentionally, as the majority of access to the
 | 
| +	// structCodecs map will be in parallel and will be to read an existing codec.
 | 
| +	// There's no reason to serialize goroutines on every
 | 
| +	// gae.RawDatastore.{Get,Put}{,Multi} call.
 | 
| +	structCodecsMutex sync.RWMutex
 | 
| +	structCodecs      = map[reflect.Type]*structCodec{}
 | 
| +)
 | 
| +
 | 
| +// validPropertyName returns whether name consists of one or more valid Go
 | 
| +// identifiers joined by ".".
 | 
| +func validPropertyName(name string) bool {
 | 
| +	if name == "" {
 | 
| +		return false
 | 
| +	}
 | 
| +	for _, s := range strings.Split(name, ".") {
 | 
| +		if s == "" {
 | 
| +			return false
 | 
| +		}
 | 
| +		first := true
 | 
| +		for _, c := range s {
 | 
| +			if first {
 | 
| +				first = false
 | 
| +				if c != '_' && !unicode.IsLetter(c) {
 | 
| +					return false
 | 
| +				}
 | 
| +			} else {
 | 
| +				if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
 | 
| +					return false
 | 
| +				}
 | 
| +			}
 | 
| +		}
 | 
| +	}
 | 
| +	return true
 | 
| +}
 | 
| +
 | 
| +var (
 | 
| +	errRecursiveStruct = fmt.Errorf("(internal): struct type is recursively defined")
 | 
| +)
 | 
| +
 | 
| +func getStructCodecLocked(t reflect.Type) (c *structCodec) {
 | 
| +	if c, ok := structCodecs[t]; ok {
 | 
| +		return c
 | 
| +	}
 | 
| +
 | 
| +	me := func(fmtStr string, args ...interface{}) error {
 | 
| +		return fmt.Errorf(fmtStr, args...)
 | 
| +	}
 | 
| +
 | 
| +	c = &structCodec{
 | 
| +		byIndex:   make([]structTag, t.NumField()),
 | 
| +		byName:    make(map[string]int, t.NumField()),
 | 
| +		bySpecial: make(map[string]int, t.NumField()),
 | 
| +		problem:   errRecursiveStruct, // we'll clear this later if it's not recursive
 | 
| +	}
 | 
| +	defer func() {
 | 
| +		// If the codec has a problem, free up the indexes
 | 
| +		if c.problem != nil {
 | 
| +			c.byIndex = nil
 | 
| +			c.byName = nil
 | 
| +			c.bySpecial = nil
 | 
| +		}
 | 
| +	}()
 | 
| +	structCodecs[t] = c
 | 
| +
 | 
| +	for i := range c.byIndex {
 | 
| +		st := &c.byIndex[i]
 | 
| +		f := t.Field(i)
 | 
| +		name := f.Tag.Get("gae")
 | 
| +		opts := ""
 | 
| +		if i := strings.Index(name, ","); i != -1 {
 | 
| +			name, opts = name[:i], name[i+1:]
 | 
| +		}
 | 
| +		switch {
 | 
| +		case name == "":
 | 
| +			if !f.Anonymous {
 | 
| +				name = f.Name
 | 
| +			}
 | 
| +		case name[0] == '$':
 | 
| +			name = name[1:]
 | 
| +			if _, ok := c.bySpecial[name]; ok {
 | 
| +				c.problem = me("special field %q set multiple times", "$"+name)
 | 
| +				return
 | 
| +			}
 | 
| +			c.bySpecial[name] = i
 | 
| +			st.specialVal = opts
 | 
| +			fallthrough
 | 
| +		case name == "-":
 | 
| +			st.name = "-"
 | 
| +			continue
 | 
| +		default:
 | 
| +			if !validPropertyName(name) {
 | 
| +				c.problem = me("struct tag has invalid property name: %q", name)
 | 
| +				return
 | 
| +			}
 | 
| +		}
 | 
| +		if f.PkgPath != "" { // field is unexported, so don't bother doing more.
 | 
| +			st.name = "-"
 | 
| +			continue
 | 
| +		}
 | 
| +
 | 
| +		substructType := reflect.Type(nil)
 | 
| +		ft := f.Type
 | 
| +		if reflect.PtrTo(ft).Implements(typeOfDSPropertyConverter) {
 | 
| +			st.convert = true
 | 
| +		} else {
 | 
| +			switch f.Type.Kind() {
 | 
| +			case reflect.Struct:
 | 
| +				if ft != typeOfTime && ft != typeOfGeoPoint {
 | 
| +					substructType = ft
 | 
| +				}
 | 
| +			case reflect.Slice:
 | 
| +				if reflect.PtrTo(ft.Elem()).Implements(typeOfDSPropertyConverter) {
 | 
| +					st.convert = true
 | 
| +				} else if ft.Elem().Kind() == reflect.Struct {
 | 
| +					substructType = ft.Elem()
 | 
| +				}
 | 
| +				st.isSlice = ft.Elem().Kind() != reflect.Uint8
 | 
| +				c.hasSlice = c.hasSlice || st.isSlice
 | 
| +			case reflect.Interface:
 | 
| +				if ft != typeOfDSKey {
 | 
| +					c.problem = me("field %q has non-concrete interface type %s",
 | 
| +						f.Name, f.Type)
 | 
| +					return
 | 
| +				}
 | 
| +			}
 | 
| +		}
 | 
| +
 | 
| +		if substructType != nil {
 | 
| +			sub := getStructCodecLocked(substructType)
 | 
| +			if sub.problem != nil {
 | 
| +				if sub.problem == errRecursiveStruct {
 | 
| +					c.problem = me("field %q is recursively defined", f.Name)
 | 
| +				} else {
 | 
| +					c.problem = me("field %q has problem: %s", f.Name, sub.problem)
 | 
| +				}
 | 
| +				return
 | 
| +			}
 | 
| +			st.substructCodec = sub
 | 
| +			if st.isSlice && sub.hasSlice {
 | 
| +				c.problem = me(
 | 
| +					"flattening nested structs leads to a slice of slices: field %q",
 | 
| +					f.Name)
 | 
| +				return
 | 
| +			}
 | 
| +			c.hasSlice = c.hasSlice || sub.hasSlice
 | 
| +			if name != "" {
 | 
| +				name += "."
 | 
| +			}
 | 
| +			for relName := range sub.byName {
 | 
| +				absName := name + relName
 | 
| +				if _, ok := c.byName[absName]; ok {
 | 
| +					c.problem = me("struct tag has repeated property name: %q", absName)
 | 
| +					return
 | 
| +				}
 | 
| +				c.byName[absName] = i
 | 
| +			}
 | 
| +		} else {
 | 
| +			if !st.convert { // check the underlying static type of the field
 | 
| +				t := ft
 | 
| +				if st.isSlice {
 | 
| +					t = t.Elem()
 | 
| +				}
 | 
| +				v := reflect.New(t).Elem().Interface()
 | 
| +				v, _ = gae.DSUpconvertUnderlyingType(v, t)
 | 
| +				if _, err := gae.DSPropertyTypeOf(v, false); err != nil {
 | 
| +					c.problem = me("field %q has invalid type: %s", name, ft)
 | 
| +					return
 | 
| +				}
 | 
| +			}
 | 
| +
 | 
| +			if _, ok := c.byName[name]; ok {
 | 
| +				c.problem = me("struct tag has repeated property name: %q", name)
 | 
| +				return
 | 
| +			}
 | 
| +			c.byName[name] = i
 | 
| +		}
 | 
| +		st.name = name
 | 
| +		st.noIndex = opts == "noindex"
 | 
| +	}
 | 
| +	if c.problem == errRecursiveStruct {
 | 
| +		c.problem = nil
 | 
| +	}
 | 
| +	return
 | 
| +}
 | 
| 
 |