| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // HEAVILY adapted from github.com/golang/appengine/datastore | 5 // HEAVILY adapted from github.com/golang/appengine/datastore |
| 6 | 6 |
| 7 package datastore | 7 package datastore |
| 8 | 8 |
| 9 import ( | 9 import ( |
| 10 "fmt" | 10 "fmt" |
| 11 "reflect" | 11 "reflect" |
| 12 "strconv" | 12 "strconv" |
| 13 "strings" | 13 "strings" |
| 14 "sync" | 14 "sync" |
| 15 "unicode" | 15 "unicode" |
| 16 | 16 |
| 17 "github.com/luci/luci-go/common/errors" | 17 "github.com/luci/luci-go/common/errors" |
| 18 ) | 18 ) |
| 19 | 19 |
| 20 // Entities with more than this many indexed properties will not be saved. | 20 // Entities with more than this many indexed properties will not be saved. |
| 21 const maxIndexedProperties = 20000 | 21 const maxIndexedProperties = 20000 |
| 22 | 22 |
| 23 type structTag struct { | 23 type structTag struct { |
| 24 name string | 24 name string |
| 25 idxSetting IndexSetting | 25 idxSetting IndexSetting |
| 26 isSlice bool | 26 isSlice bool |
| 27 substructCodec *structCodec | 27 substructCodec *structCodec |
| 28 convert bool | 28 convert bool |
| 29 metaVal interface{} | 29 metaVal interface{} |
| 30 isExtra bool |
| 30 canSet bool | 31 canSet bool |
| 31 } | 32 } |
| 32 | 33 |
| 33 type structCodec struct { | 34 type structCodec struct { |
| 34 » byMeta map[string]int | 35 » byMeta map[string]int |
| 35 » byName map[string]int | 36 » byName map[string]int |
| 37 » bySpecial map[string]int |
| 38 |
| 36 byIndex []structTag | 39 byIndex []structTag |
| 37 hasSlice bool | 40 hasSlice bool |
| 38 problem error | 41 problem error |
| 39 } | 42 } |
| 40 | 43 |
| 41 type structPLS struct { | 44 type structPLS struct { |
| 42 o reflect.Value | 45 o reflect.Value |
| 43 c *structCodec | 46 c *structCodec |
| 44 } | 47 } |
| 45 | 48 |
| 46 var _ PropertyLoadSaver = (*structPLS)(nil) | 49 var _ PropertyLoadSaver = (*structPLS)(nil) |
| 47 | 50 |
| 48 // typeMismatchReason returns a string explaining why the property p could not | 51 // typeMismatchReason returns a string explaining why the property p could not |
| 49 // be stored in an entity field of type v.Type(). | 52 // be stored in an entity field of type v.Type(). |
| 50 func typeMismatchReason(val interface{}, v reflect.Value) string { | 53 func typeMismatchReason(val interface{}, v reflect.Value) string { |
| 51 entityType := reflect.TypeOf(val) | 54 entityType := reflect.TypeOf(val) |
| 52 return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type()) | 55 return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type()) |
| 53 } | 56 } |
| 54 | 57 |
| 55 func (p *structPLS) Load(propMap PropertyMap) error { | 58 func (p *structPLS) Load(propMap PropertyMap) error { |
| 56 convFailures := errors.MultiError(nil) | 59 convFailures := errors.MultiError(nil) |
| 57 | 60 |
| 61 useExtra := false |
| 62 extra := (*PropertyMap)(nil) |
| 63 if i, ok := p.c.bySpecial["extra"]; ok { |
| 64 useExtra = true |
| 65 f := p.c.byIndex[i] |
| 66 if f.canSet { |
| 67 extra = p.o.Field(i).Addr().Interface().(*PropertyMap) |
| 68 } |
| 69 } |
| 58 t := reflect.Type(nil) | 70 t := reflect.Type(nil) |
| 59 for name, props := range propMap { | 71 for name, props := range propMap { |
| 60 multiple := len(props) > 1 | 72 multiple := len(props) > 1 |
| 61 for i, prop := range props { | 73 for i, prop := range props { |
| 62 if reason := loadInner(p.c, p.o, i, name, prop, multiple
); reason != "" { | 74 if reason := loadInner(p.c, p.o, i, name, prop, multiple
); reason != "" { |
| 63 » » » » if t == nil { | 75 » » » » if useExtra { |
| 64 » » » » » t = p.o.Type() | 76 » » » » » if extra != nil { |
| 77 » » » » » » if *extra == nil { |
| 78 » » » » » » » *extra = make(PropertyMa
p, 1) |
| 79 » » » » » » } |
| 80 » » » » » » (*extra)[name] = props |
| 81 » » » » » } |
| 82 » » » » » break // go to the next property in prop
Map |
| 83 » » » » } else { |
| 84 » » » » » if t == nil { |
| 85 » » » » » » t = p.o.Type() |
| 86 » » » » » } |
| 87 » » » » » convFailures = append(convFailures, &Err
FieldMismatch{ |
| 88 » » » » » » StructType: t, |
| 89 » » » » » » FieldName: name, |
| 90 » » » » » » Reason: reason, |
| 91 » » » » » }) |
| 65 } | 92 } |
| 66 convFailures = append(convFailures, &ErrFieldMis
match{ | |
| 67 StructType: t, | |
| 68 FieldName: name, | |
| 69 Reason: reason, | |
| 70 }) | |
| 71 } | 93 } |
| 72 } | 94 } |
| 73 } | 95 } |
| 74 | 96 |
| 75 if len(convFailures) > 0 { | 97 if len(convFailures) > 0 { |
| 76 return convFailures | 98 return convFailures |
| 77 } | 99 } |
| 78 | 100 |
| 79 return nil | 101 return nil |
| 80 } | 102 } |
| (...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 246 if prop.IndexSetting() == ShouldIndex { | 268 if prop.IndexSetting() == ShouldIndex { |
| 247 idxCount++ | 269 idxCount++ |
| 248 if idxCount > maxIndexedProperties { | 270 if idxCount > maxIndexedProperties { |
| 249 return errors.New("gae: too many indexed propert
ies") | 271 return errors.New("gae: too many indexed propert
ies") |
| 250 } | 272 } |
| 251 } | 273 } |
| 252 return nil | 274 return nil |
| 253 } | 275 } |
| 254 | 276 |
| 255 for i, st := range p.c.byIndex { | 277 for i, st := range p.c.byIndex { |
| 256 » » if st.name == "-" { | 278 » » if st.name == "-" || st.isExtra { |
| 257 continue | 279 continue |
| 258 } | 280 } |
| 259 name := st.name | 281 name := st.name |
| 260 if prefix != "" { | 282 if prefix != "" { |
| 261 name = prefix + name | 283 name = prefix + name |
| 262 } | 284 } |
| 263 v := p.o.Field(i) | 285 v := p.o.Field(i) |
| 264 is1 := is | 286 is1 := is |
| 265 if st.idxSetting == NoIndex { | 287 if st.idxSetting == NoIndex { |
| 266 is1 = NoIndex | 288 is1 = NoIndex |
| 267 } | 289 } |
| 268 if st.isSlice { | 290 if st.isSlice { |
| 269 for j := 0; j < v.Len(); j++ { | 291 for j := 0; j < v.Len(); j++ { |
| 270 if err = saveProp(name, is1, v.Index(j), &st); e
rr != nil { | 292 if err = saveProp(name, is1, v.Index(j), &st); e
rr != nil { |
| 271 return | 293 return |
| 272 } | 294 } |
| 273 } | 295 } |
| 274 } else { | 296 } else { |
| 275 if err = saveProp(name, is1, v, &st); err != nil { | 297 if err = saveProp(name, is1, v, &st); err != nil { |
| 276 return | 298 return |
| 277 } | 299 } |
| 278 } | 300 } |
| 279 } | 301 } |
| 302 |
| 303 if i, ok := p.c.bySpecial["extra"]; ok { |
| 304 if p.c.byIndex[i].name != "-" { |
| 305 for fullName, vals := range p.o.Field(i).Interface().(Pr
opertyMap) { |
| 306 if _, ok := propMap[fullName]; !ok { |
| 307 propMap[fullName] = vals |
| 308 } |
| 309 } |
| 310 } |
| 311 } |
| 312 |
| 280 return | 313 return |
| 281 } | 314 } |
| 282 | 315 |
| 283 func (p *structPLS) GetMeta(key string) (interface{}, bool) { | 316 func (p *structPLS) GetMeta(key string) (interface{}, bool) { |
| 284 if idx, ok := p.c.byMeta[key]; ok { | 317 if idx, ok := p.c.byMeta[key]; ok { |
| 285 if val, ok := p.getMetaFor(idx); ok { | 318 if val, ok := p.getMetaFor(idx); ok { |
| 286 return val, true | 319 return val, true |
| 287 } | 320 } |
| 288 } else if key == "kind" { | 321 } else if key == "kind" { |
| 289 return p.getDefaultKind(), true | 322 return p.getDefaultKind(), true |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 412 func getStructCodecLocked(t reflect.Type) (c *structCodec) { | 445 func getStructCodecLocked(t reflect.Type) (c *structCodec) { |
| 413 if c, ok := structCodecs[t]; ok { | 446 if c, ok := structCodecs[t]; ok { |
| 414 return c | 447 return c |
| 415 } | 448 } |
| 416 | 449 |
| 417 me := func(fmtStr string, args ...interface{}) error { | 450 me := func(fmtStr string, args ...interface{}) error { |
| 418 return fmt.Errorf(fmtStr, args...) | 451 return fmt.Errorf(fmtStr, args...) |
| 419 } | 452 } |
| 420 | 453 |
| 421 c = &structCodec{ | 454 c = &structCodec{ |
| 422 » » byIndex: make([]structTag, t.NumField()), | 455 » » byIndex: make([]structTag, t.NumField()), |
| 423 » » byName: make(map[string]int, t.NumField()), | 456 » » byName: make(map[string]int, t.NumField()), |
| 424 » » byMeta: make(map[string]int, t.NumField()), | 457 » » byMeta: make(map[string]int, t.NumField()), |
| 458 » » bySpecial: make(map[string]int, 1), |
| 425 | 459 |
| 426 problem: errRecursiveStruct, // we'll clear this later if it's n
ot recursive | 460 problem: errRecursiveStruct, // we'll clear this later if it's n
ot recursive |
| 427 } | 461 } |
| 428 defer func() { | 462 defer func() { |
| 429 // If the codec has a problem, free up the indexes | 463 // If the codec has a problem, free up the indexes |
| 430 if c.problem != nil { | 464 if c.problem != nil { |
| 431 c.byIndex = nil | 465 c.byIndex = nil |
| 432 c.byName = nil | 466 c.byName = nil |
| 433 c.byMeta = nil | 467 c.byMeta = nil |
| 434 } | 468 } |
| 435 }() | 469 }() |
| 436 structCodecs[t] = c | 470 structCodecs[t] = c |
| 437 | 471 |
| 438 for i := range c.byIndex { | 472 for i := range c.byIndex { |
| 439 st := &c.byIndex[i] | 473 st := &c.byIndex[i] |
| 440 f := t.Field(i) | 474 f := t.Field(i) |
| 441 ft := f.Type | 475 ft := f.Type |
| 442 | 476 |
| 443 name := f.Tag.Get("gae") | 477 name := f.Tag.Get("gae") |
| 444 opts := "" | 478 opts := "" |
| 445 if i := strings.Index(name, ","); i != -1 { | 479 if i := strings.Index(name, ","); i != -1 { |
| 446 name, opts = name[:i], name[i+1:] | 480 name, opts = name[:i], name[i+1:] |
| 447 } | 481 } |
| 448 st.canSet = f.PkgPath == "" // blank == exported | 482 st.canSet = f.PkgPath == "" // blank == exported |
| 483 if opts == "extra" { |
| 484 if _, ok := c.bySpecial["extra"]; ok { |
| 485 c.problem = me("struct has multiple fields tagge
d as 'extra'") |
| 486 return |
| 487 } |
| 488 if name != "" && name != "-" { |
| 489 c.problem = me("struct 'extra' field has invalid
name %s, expecing `` or `-`", name) |
| 490 return |
| 491 } |
| 492 if ft != typeOfPropertyMap { |
| 493 c.problem = me("struct 'extra' field has invalid
type %s, expecing PropertyMap", ft) |
| 494 return |
| 495 } |
| 496 st.isExtra = true |
| 497 st.name = name |
| 498 c.bySpecial["extra"] = i |
| 499 continue |
| 500 } |
| 449 st.convert = reflect.PtrTo(ft).Implements(typeOfPropertyConverte
r) | 501 st.convert = reflect.PtrTo(ft).Implements(typeOfPropertyConverte
r) |
| 450 switch { | 502 switch { |
| 451 case name == "": | 503 case name == "": |
| 452 if !f.Anonymous { | 504 if !f.Anonymous { |
| 453 name = f.Name | 505 name = f.Name |
| 454 } | 506 } |
| 455 case name[0] == '$': | 507 case name[0] == '$': |
| 456 name = name[1:] | 508 name = name[1:] |
| 457 if _, ok := c.byMeta[name]; ok { | 509 if _, ok := c.byMeta[name]; ok { |
| 458 c.problem = me("meta field %q set multiple times
", "$"+name) | 510 c.problem = me("meta field %q set multiple times
", "$"+name) |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 583 switch val { | 635 switch val { |
| 584 case "on", "On", "true": | 636 case "on", "On", "true": |
| 585 return true, nil | 637 return true, nil |
| 586 case "off", "Off", "false": | 638 case "off", "Off", "false": |
| 587 return false, nil | 639 return false, nil |
| 588 } | 640 } |
| 589 return nil, fmt.Errorf("Toggle field has bad/missing default, go
t %q", val) | 641 return nil, fmt.Errorf("Toggle field has bad/missing default, go
t %q", val) |
| 590 } | 642 } |
| 591 return nil, fmt.Errorf("helper: meta field with bad type/value %s/%q", t
, val) | 643 return nil, fmt.Errorf("helper: meta field with bad type/value %s/%q", t
, val) |
| 592 } | 644 } |
| OLD | NEW |