| 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
|
| +}
|
|
|