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