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

Side by Side Diff: service/datastore/pls_impl.go

Issue 1525453002: Handle unexpected entity fields gracefully. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: Created 5 years 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 unified diff | Download patch
OLDNEW
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698