| 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
|
| index bfe066ea4871b9713e6ade287f3a284fe316d5e7..9e94217c920d39f6944c62087655c63bf664c0de 100644
|
| --- a/go/src/infra/gae/libs/gae/helper/datastore_impl.go
|
| +++ b/go/src/infra/gae/libs/gae/helper/datastore_impl.go
|
| @@ -9,12 +9,14 @@ package helper
|
| import (
|
| "errors"
|
| "fmt"
|
| - "infra/gae/libs/gae"
|
| "reflect"
|
| + "strconv"
|
| "strings"
|
| "sync"
|
| "time"
|
| "unicode"
|
| +
|
| + "infra/gae/libs/gae"
|
| )
|
|
|
| // Entities with more than this many indexed properties will not be saved.
|
| @@ -25,25 +27,29 @@ var (
|
| typeOfDSPropertyConverter = reflect.TypeOf((*gae.DSPropertyConverter)(nil)).Elem()
|
| typeOfGeoPoint = reflect.TypeOf(gae.DSGeoPoint{})
|
| typeOfTime = reflect.TypeOf(time.Time{})
|
| + typeOfString = reflect.TypeOf("")
|
| + typeOfInt64 = reflect.TypeOf(int64(0))
|
| + typeOfBool = reflect.TypeOf(true)
|
|
|
| valueOfnilDSKey = reflect.Zero(typeOfDSKey)
|
| )
|
|
|
| type structTag struct {
|
| name string
|
| - noIndex bool
|
| + idxSetting gae.IndexSetting
|
| isSlice bool
|
| substructCodec *structCodec
|
| convert bool
|
| - specialVal string
|
| + metaVal interface{}
|
| + canSet bool
|
| }
|
|
|
| type structCodec struct {
|
| - bySpecial map[string]int
|
| - byName map[string]int
|
| - byIndex []structTag
|
| - hasSlice bool
|
| - problem error
|
| + byMeta map[string]int
|
| + byName map[string]int
|
| + byIndex []structTag
|
| + hasSlice bool
|
| + problem error
|
| }
|
|
|
| type structPLS struct {
|
| @@ -51,7 +57,7 @@ type structPLS struct {
|
| c *structCodec
|
| }
|
|
|
| -var _ gae.DSStructPLS = (*structPLS)(nil)
|
| +var _ gae.DSPropertyLoadSaver = (*structPLS)(nil)
|
|
|
| // typeMismatchReason returns a string explaining why the property p could not
|
| // be stored in an entity field of type v.Type().
|
| @@ -60,22 +66,35 @@ func typeMismatchReason(val interface{}, v reflect.Value) string {
|
| 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
|
| +func (p *structPLS) Load(propMap gae.DSPropertyMap) error {
|
| + if err := p.Problem(); err != nil {
|
| + return err
|
| }
|
|
|
| - t := p.o.Type()
|
| + convFailures := gae.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 != "" {
|
| - convFailures = append(convFailures, fmt.Sprintf(
|
| - "cannot load field %q into a %q: %s", name, t, reason))
|
| + if t == nil {
|
| + t = p.o.Type()
|
| + }
|
| + convFailures = append(convFailures, &gae.ErrDSFieldMismatch{
|
| + StructType: t,
|
| + FieldName: name,
|
| + Reason: reason,
|
| + })
|
| }
|
| }
|
| }
|
| - return
|
| +
|
| + if len(convFailures) > 0 {
|
| + return convFailures
|
| + }
|
| +
|
| + return nil
|
| }
|
|
|
| func loadInner(codec *structCodec, structValue reflect.Value, index int, name string, p gae.DSProperty, requireSlice bool) string {
|
| @@ -222,38 +241,61 @@ func loadInner(codec *structCodec, structValue reflect.Value, index int, name st
|
| return ""
|
| }
|
|
|
| -func (p *structPLS) Save() (gae.DSPropertyMap, error) {
|
| - ret := gae.DSPropertyMap{}
|
| - idxCount := 0
|
| - if err := p.save(ret, &idxCount, "", false); err != nil {
|
| +func (p *structPLS) Save(withMeta bool) (gae.DSPropertyMap, error) {
|
| + size := len(p.c.byName)
|
| + if withMeta {
|
| + size += len(p.c.byMeta)
|
| + }
|
| + ret := make(gae.DSPropertyMap, size)
|
| + if _, err := p.save(ret, "", gae.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 := gae.DSProperty{}
|
| + if err = p.SetValue(val, gae.NoIndex); err != nil {
|
| + return nil, err
|
| + }
|
| + ret["$"+k] = []gae.DSProperty{p}
|
| + }
|
| + }
|
| return ret, nil
|
| }
|
|
|
| -func (p *structPLS) save(propMap gae.DSPropertyMap, idxCount *int, prefix string, noIndex bool) (err error) {
|
| +func (p *structPLS) save(propMap gae.DSPropertyMap, prefix string, is gae.IndexSetting) (idxCount int, err error) {
|
| if err = p.Problem(); err != nil {
|
| return
|
| }
|
|
|
| - saveProp := func(name string, ni bool, v reflect.Value, st *structTag) (err error) {
|
| + saveProp := func(name string, si gae.IndexSetting, v reflect.Value, st *structTag) (err error) {
|
| if st.substructCodec != nil {
|
| - return (&structPLS{v, st.substructCodec}).save(propMap, idxCount, name, ni)
|
| + 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 := gae.DSProperty{}
|
| if st.convert {
|
| prop, err = v.Addr().Interface().(gae.DSPropertyConverter).ToDSProperty()
|
| } else {
|
| - err = prop.SetValue(v.Interface(), ni)
|
| + err = prop.SetValue(v.Interface(), si)
|
| }
|
| if err != nil {
|
| return err
|
| }
|
| propMap[name] = append(propMap[name], prop)
|
| - if !prop.NoIndex() {
|
| - *idxCount++
|
| - if *idxCount > maxIndexedProperties {
|
| + if prop.IndexSetting() == gae.ShouldIndex {
|
| + idxCount++
|
| + if idxCount > maxIndexedProperties {
|
| return errors.New("gae: too many indexed properties")
|
| }
|
| }
|
| @@ -269,53 +311,55 @@ func (p *structPLS) save(propMap gae.DSPropertyMap, idxCount *int, prefix string
|
| name = prefix + name
|
| }
|
| v := p.o.Field(i)
|
| - noIndex1 := noIndex || st.noIndex
|
| + is1 := is
|
| + if st.idxSetting == gae.NoIndex {
|
| + is1 = gae.NoIndex
|
| + }
|
| if st.isSlice {
|
| for j := 0; j < v.Len(); j++ {
|
| - if err := saveProp(name, noIndex1, v.Index(j), &st); err != nil {
|
| - return err
|
| + if err = saveProp(name, is1, v.Index(j), &st); err != nil {
|
| + return
|
| }
|
| }
|
| } else {
|
| - if err := saveProp(name, noIndex1, v, &st); err != nil {
|
| - return err
|
| + if err = saveProp(name, is1, v, &st); err != nil {
|
| + return
|
| }
|
| }
|
| }
|
| - return nil
|
| + return
|
| }
|
|
|
| -func (p *structPLS) GetSpecial(key string) (val string, current interface{}, err error) {
|
| - if err = p.Problem(); err != nil {
|
| - return
|
| +func (p *structPLS) GetMeta(key string) (interface{}, error) {
|
| + if err := p.Problem(); err != nil {
|
| + return nil, err
|
| }
|
| - idx, ok := p.c.bySpecial[key]
|
| + idx, ok := p.c.byMeta[key]
|
| if !ok {
|
| - err = gae.ErrDSSpecialFieldUnset
|
| - return
|
| + return nil, gae.ErrDSMetaFieldUnset
|
| }
|
| - val = p.c.byIndex[idx].specialVal
|
| + st := p.c.byIndex[idx]
|
| + val := st.metaVal
|
| f := p.o.Field(idx)
|
| - if f.CanSet() {
|
| - current = f.Interface()
|
| + if st.canSet {
|
| + if !reflect.DeepEqual(reflect.Zero(f.Type()).Interface(), f.Interface()) {
|
| + val = f.Interface()
|
| + }
|
| }
|
| - return
|
| + return val, nil
|
| }
|
|
|
| -func (p *structPLS) SetSpecial(key string, val interface{}) (err error) {
|
| +func (p *structPLS) SetMeta(key string, val interface{}) (err error) {
|
| if err = p.Problem(); err != nil {
|
| return
|
| }
|
| - idx, ok := p.c.bySpecial[key]
|
| + idx, ok := p.c.byMeta[key]
|
| if !ok {
|
| - return gae.ErrDSSpecialFieldUnset
|
| + return gae.ErrDSMetaFieldUnset
|
| + }
|
| + if !p.c.byIndex[idx].canSet {
|
| + return fmt.Errorf("gae/helper: cannot set meta %q: unexported field", key)
|
| }
|
| - 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
|
| }
|
| @@ -372,17 +416,17 @@ func getStructCodecLocked(t reflect.Type) (c *structCodec) {
|
| }
|
|
|
| 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
|
| + 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.bySpecial = nil
|
| + c.byMeta = nil
|
| }
|
| }()
|
| structCodecs[t] = c
|
| @@ -395,6 +439,7 @@ func getStructCodecLocked(t reflect.Type) (c *structCodec) {
|
| 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 {
|
| @@ -402,12 +447,17 @@ func getStructCodecLocked(t reflect.Type) (c *structCodec) {
|
| }
|
| case name[0] == '$':
|
| name = name[1:]
|
| - if _, ok := c.bySpecial[name]; ok {
|
| - c.problem = me("special field %q set multiple times", "$"+name)
|
| + 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
|
| }
|
| - c.bySpecial[name] = i
|
| - st.specialVal = opts
|
| + st.metaVal = mv
|
| fallthrough
|
| case name == "-":
|
| st.name = "-"
|
| @@ -418,7 +468,7 @@ func getStructCodecLocked(t reflect.Type) (c *structCodec) {
|
| return
|
| }
|
| }
|
| - if f.PkgPath != "" { // field is unexported, so don't bother doing more.
|
| + if !st.canSet {
|
| st.name = "-"
|
| continue
|
| }
|
| @@ -500,10 +550,25 @@ func getStructCodecLocked(t reflect.Type) (c *structCodec) {
|
| c.byName[name] = i
|
| }
|
| st.name = name
|
| - st.noIndex = opts == "noindex"
|
| + if opts == "noindex" {
|
| + st.idxSetting = gae.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)
|
| + }
|
| + return nil, fmt.Errorf("helper: meta field with bad type/value %s/%s", t, val)
|
| +}
|
|
|