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

Unified Diff: go/src/infra/gae/libs/gae/helper/datastore_impl.go

Issue 1222903002: Refactor current GAE abstraction library to be free of the SDK* (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: more fixes 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: 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
+}

Powered by Google App Engine
This is Rietveld 408576698