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

Unified Diff: service/rawdatastore/datastore_impl.go

Issue 1259593005: Add 'user friendly' datastore API. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: 100% coverage of new code 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: service/rawdatastore/datastore_impl.go
diff --git a/service/rawdatastore/datastore_impl.go b/service/rawdatastore/datastore_impl.go
deleted file mode 100644
index 1b10daffe5676d61c1741675d052828323c13de4..0000000000000000000000000000000000000000
--- a/service/rawdatastore/datastore_impl.go
+++ /dev/null
@@ -1,581 +0,0 @@
-// 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 rawdatastore
-
-import (
- "fmt"
- "reflect"
- "strconv"
- "strings"
- "sync"
- "time"
- "unicode"
-
- "github.com/luci/gae/service/blobstore"
- "github.com/luci/luci-go/common/errors"
-)
-
-// Entities with more than this many indexed properties will not be saved.
-const maxIndexedProperties = 20000
-
-type structTag struct {
- name string
- idxSetting IndexSetting
- isSlice bool
- substructCodec *structCodec
- convert bool
- metaVal interface{}
- canSet bool
-}
-
-type structCodec struct {
- byMeta map[string]int
- byName map[string]int
- byIndex []structTag
- hasSlice bool
- problem error
-}
-
-type structPLS struct {
- o reflect.Value
- c *structCodec
-}
-
-var _ PropertyLoadSaver = (*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 PropertyMap) error {
- if err := p.Problem(); err != nil {
- return err
- }
-
- convFailures := errors.MultiError(nil)
-
- t := reflect.Type(nil)
- 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 != "" {
- if t == nil {
- t = p.o.Type()
- }
- convFailures = append(convFailures, &ErrFieldMismatch{
- StructType: t,
- FieldName: name,
- Reason: reason,
- })
- }
- }
- }
-
- if len(convFailures) > 0 {
- return convFailures
- }
-
- return nil
-}
-
-func loadInner(codec *structCodec, structValue reflect.Value, index int, name string, p Property, 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().(PropertyConverter); ok {
- err := conv.FromProperty(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(typeOfKey) {
- 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 blobstore.Key:
- 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.(Key)
- 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.(GeoPoint)
- 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 ByteString:
- 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(withMeta bool) (PropertyMap, error) {
- size := len(p.c.byName)
- if withMeta {
- size += len(p.c.byMeta)
- }
- ret := make(PropertyMap, size)
- if _, err := p.save(ret, "", ShouldIndex); err != nil {
- return nil, err
- }
- if withMeta {
- for k := range p.c.byMeta {
- val, err := p.GetMeta(k)
- if err != nil {
- return nil, err // TODO(riannucci): should these be ignored?
- }
- p := Property{}
- if err = p.SetValue(val, NoIndex); err != nil {
- return nil, err
- }
- ret["$"+k] = []Property{p}
- }
- }
- return ret, nil
-}
-
-func (p *structPLS) save(propMap PropertyMap, prefix string, is IndexSetting) (idxCount int, err error) {
- if err = p.Problem(); err != nil {
- return
- }
-
- saveProp := func(name string, si IndexSetting, v reflect.Value, st *structTag) (err error) {
- if st.substructCodec != nil {
- count, err := (&structPLS{v, st.substructCodec}).save(propMap, name, si)
- if err == nil {
- idxCount += count
- if idxCount > maxIndexedProperties {
- err = errors.New("gae: too many indexed properties")
- }
- }
- return err
- }
-
- prop := Property{}
- if st.convert {
- prop, err = v.Addr().Interface().(PropertyConverter).ToProperty()
- } else {
- err = prop.SetValue(v.Interface(), si)
- }
- if err != nil {
- return err
- }
- propMap[name] = append(propMap[name], prop)
- if prop.IndexSetting() == ShouldIndex {
- 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)
- is1 := is
- if st.idxSetting == NoIndex {
- is1 = NoIndex
- }
- if st.isSlice {
- for j := 0; j < v.Len(); j++ {
- if err = saveProp(name, is1, v.Index(j), &st); err != nil {
- return
- }
- }
- } else {
- if err = saveProp(name, is1, v, &st); err != nil {
- return
- }
- }
- }
- return
-}
-
-func (p *structPLS) GetMeta(key string) (interface{}, error) {
- if err := p.Problem(); err != nil {
- return nil, err
- }
- idx, ok := p.c.byMeta[key]
- if !ok {
- return nil, ErrMetaFieldUnset
- }
- st := p.c.byIndex[idx]
- val := st.metaVal
- f := p.o.Field(idx)
- if st.canSet {
- if !reflect.DeepEqual(reflect.Zero(f.Type()).Interface(), f.Interface()) {
- val = f.Interface()
- if bf, ok := val.(Toggle); ok {
- val = bf == On // true if On, otherwise false
- }
- }
- }
- return val, nil
-}
-
-func (p *structPLS) SetMeta(key string, val interface{}) (err error) {
- if err = p.Problem(); err != nil {
- return
- }
- idx, ok := p.c.byMeta[key]
- if !ok {
- return ErrMetaFieldUnset
- }
- if !p.c.byIndex[idx].canSet {
- return fmt.Errorf("gae/helper: cannot set meta %q: unexported field", key)
- }
- // setting a BoolField
- if b, ok := val.(bool); ok {
- if b {
- val = On
- } else {
- val = Off
- }
- }
- 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()),
- byMeta: 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.byMeta = 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:]
- }
- st.canSet = f.PkgPath == "" // blank == exported
- switch {
- case name == "":
- if !f.Anonymous {
- name = f.Name
- }
- case name[0] == '$':
- name = name[1:]
- if _, ok := c.byMeta[name]; ok {
- c.problem = me("meta field %q set multiple times", "$"+name)
- return
- }
- c.byMeta[name] = i
- mv, err := convertMeta(opts, f.Type)
- if err != nil {
- c.problem = me("meta field %q has bad type: %s", "$"+name, err)
- return
- }
- st.metaVal = mv
- fallthrough
- case name == "-":
- st.name = "-"
- continue
- default:
- if !validPropertyName(name) {
- c.problem = me("struct tag has invalid property name: %q", name)
- return
- }
- }
- if !st.canSet {
- st.name = "-"
- continue
- }
-
- substructType := reflect.Type(nil)
- ft := f.Type
- if reflect.PtrTo(ft).Implements(typeOfPropertyConverter) {
- 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(typeOfPropertyConverter) {
- 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 != typeOfKey {
- 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, _ = UpconvertUnderlyingType(v, t)
- if _, err := PropertyTypeOf(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
- if opts == "noindex" {
- st.idxSetting = NoIndex
- }
- }
- if c.problem == errRecursiveStruct {
- c.problem = nil
- }
- return
-}
-
-func convertMeta(val string, t reflect.Type) (interface{}, error) {
- switch t {
- case typeOfString:
- return val, nil
- case typeOfInt64:
- if val == "" {
- return int64(0), nil
- }
- return strconv.ParseInt(val, 10, 64)
- case typeOfToggle:
- switch val {
- case "on", "On", "true":
- return true, nil
- case "off", "Off", "false":
- return false, nil
- }
- return nil, fmt.Errorf("Toggle field has bad/missing default, got %q", val)
- }
- return nil, fmt.Errorf("helper: meta field with bad type/value %s/%q", t, val)
-}

Powered by Google App Engine
This is Rietveld 408576698