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 |