OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 // HEAVILY adapted from github.com/golang/appengine/datastore | |
6 | |
7 package helper | |
8 | |
9 import ( | |
10 "errors" | |
11 "fmt" | |
12 "reflect" | |
13 "strconv" | |
14 "strings" | |
15 "sync" | |
16 "time" | |
17 "unicode" | |
18 | |
19 "github.com/luci/gae" | |
20 ) | |
21 | |
22 // Entities with more than this many indexed properties will not be saved. | |
23 const maxIndexedProperties = 20000 | |
24 | |
25 var ( | |
26 typeOfDSKey = reflect.TypeOf((*gae.DSKey)(nil)).Elem() | |
27 typeOfDSPropertyConverter = reflect.TypeOf((*gae.DSPropertyConverter)(ni
l)).Elem() | |
28 typeOfGeoPoint = reflect.TypeOf(gae.DSGeoPoint{}) | |
29 typeOfTime = reflect.TypeOf(time.Time{}) | |
30 typeOfString = reflect.TypeOf("") | |
31 typeOfInt64 = reflect.TypeOf(int64(0)) | |
32 typeOfBool = reflect.TypeOf(true) | |
33 | |
34 valueOfnilDSKey = reflect.Zero(typeOfDSKey) | |
35 ) | |
36 | |
37 type structTag struct { | |
38 name string | |
39 idxSetting gae.IndexSetting | |
40 isSlice bool | |
41 substructCodec *structCodec | |
42 convert bool | |
43 metaVal interface{} | |
44 canSet bool | |
45 } | |
46 | |
47 type structCodec struct { | |
48 byMeta map[string]int | |
49 byName map[string]int | |
50 byIndex []structTag | |
51 hasSlice bool | |
52 problem error | |
53 } | |
54 | |
55 type structPLS struct { | |
56 o reflect.Value | |
57 c *structCodec | |
58 } | |
59 | |
60 var _ gae.DSPropertyLoadSaver = (*structPLS)(nil) | |
61 | |
62 // typeMismatchReason returns a string explaining why the property p could not | |
63 // be stored in an entity field of type v.Type(). | |
64 func typeMismatchReason(val interface{}, v reflect.Value) string { | |
65 entityType := reflect.TypeOf(val) | |
66 return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type()) | |
67 } | |
68 | |
69 func (p *structPLS) Load(propMap gae.DSPropertyMap) error { | |
70 if err := p.Problem(); err != nil { | |
71 return err | |
72 } | |
73 | |
74 convFailures := gae.MultiError(nil) | |
75 | |
76 t := reflect.Type(nil) | |
77 for name, props := range propMap { | |
78 multiple := len(props) > 1 | |
79 for i, prop := range props { | |
80 if reason := loadInner(p.c, p.o, i, name, prop, multiple
); reason != "" { | |
81 if t == nil { | |
82 t = p.o.Type() | |
83 } | |
84 convFailures = append(convFailures, &gae.ErrDSFi
eldMismatch{ | |
85 StructType: t, | |
86 FieldName: name, | |
87 Reason: reason, | |
88 }) | |
89 } | |
90 } | |
91 } | |
92 | |
93 if len(convFailures) > 0 { | |
94 return convFailures | |
95 } | |
96 | |
97 return nil | |
98 } | |
99 | |
100 func loadInner(codec *structCodec, structValue reflect.Value, index int, name st
ring, p gae.DSProperty, requireSlice bool) string { | |
101 var v reflect.Value | |
102 // Traverse a struct's struct-typed fields. | |
103 for { | |
104 fieldIndex, ok := codec.byName[name] | |
105 if !ok { | |
106 return "no such struct field" | |
107 } | |
108 v = structValue.Field(fieldIndex) | |
109 | |
110 st := codec.byIndex[fieldIndex] | |
111 if st.substructCodec == nil { | |
112 break | |
113 } | |
114 | |
115 if v.Kind() == reflect.Slice { | |
116 for v.Len() <= index { | |
117 v.Set(reflect.Append(v, reflect.New(v.Type().Ele
m()).Elem())) | |
118 } | |
119 structValue = v.Index(index) | |
120 requireSlice = false | |
121 } else { | |
122 structValue = v | |
123 } | |
124 // Strip the "I." from "I.X". | |
125 name = name[len(st.name):] | |
126 codec = st.substructCodec | |
127 } | |
128 | |
129 doConversion := func(v reflect.Value) (string, bool) { | |
130 a := v.Addr() | |
131 if conv, ok := a.Interface().(gae.DSPropertyConverter); ok { | |
132 err := conv.FromDSProperty(p) | |
133 if err != nil { | |
134 return err.Error(), true | |
135 } | |
136 return "", true | |
137 } | |
138 return "", false | |
139 } | |
140 | |
141 if ret, ok := doConversion(v); ok { | |
142 return ret | |
143 } | |
144 | |
145 var slice reflect.Value | |
146 if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8
{ | |
147 slice = v | |
148 v = reflect.New(v.Type().Elem()).Elem() | |
149 } else if requireSlice { | |
150 return "multiple-valued property requires a slice field type" | |
151 } | |
152 | |
153 pVal := p.Value() | |
154 | |
155 if ret, ok := doConversion(v); ok { | |
156 if ret != "" { | |
157 return ret | |
158 } | |
159 } else { | |
160 knd := v.Kind() | |
161 if v.Type().Implements(typeOfDSKey) { | |
162 knd = reflect.Interface | |
163 } | |
164 switch knd { | |
165 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, re
flect.Int64: | |
166 x, ok := pVal.(int64) | |
167 if !ok && pVal != nil { | |
168 return typeMismatchReason(pVal, v) | |
169 } | |
170 if v.OverflowInt(x) { | |
171 return fmt.Sprintf("value %v overflows struct fi
eld of type %v", x, v.Type()) | |
172 } | |
173 v.SetInt(x) | |
174 case reflect.Bool: | |
175 x, ok := pVal.(bool) | |
176 if !ok && pVal != nil { | |
177 return typeMismatchReason(pVal, v) | |
178 } | |
179 v.SetBool(x) | |
180 case reflect.String: | |
181 switch x := pVal.(type) { | |
182 case gae.BSKey: | |
183 v.SetString(string(x)) | |
184 case string: | |
185 v.SetString(x) | |
186 default: | |
187 if pVal != nil { | |
188 return typeMismatchReason(pVal, v) | |
189 } | |
190 } | |
191 case reflect.Float32, reflect.Float64: | |
192 x, ok := pVal.(float64) | |
193 if !ok && pVal != nil { | |
194 return typeMismatchReason(pVal, v) | |
195 } | |
196 if v.OverflowFloat(x) { | |
197 return fmt.Sprintf("value %v overflows struct fi
eld of type %v", x, v.Type()) | |
198 } | |
199 v.SetFloat(x) | |
200 case reflect.Interface: | |
201 x, ok := pVal.(gae.DSKey) | |
202 if !ok && pVal != nil { | |
203 return typeMismatchReason(pVal, v) | |
204 } | |
205 if x != nil { | |
206 v.Set(reflect.ValueOf(x)) | |
207 } | |
208 case reflect.Struct: | |
209 switch v.Type() { | |
210 case typeOfTime: | |
211 x, ok := pVal.(time.Time) | |
212 if !ok && pVal != nil { | |
213 return typeMismatchReason(pVal, v) | |
214 } | |
215 v.Set(reflect.ValueOf(x)) | |
216 case typeOfGeoPoint: | |
217 x, ok := pVal.(gae.DSGeoPoint) | |
218 if !ok && pVal != nil { | |
219 return typeMismatchReason(pVal, v) | |
220 } | |
221 v.Set(reflect.ValueOf(x)) | |
222 default: | |
223 panic(fmt.Errorf("helper: impossible: %s", typeM
ismatchReason(pVal, v))) | |
224 } | |
225 case reflect.Slice: | |
226 switch x := pVal.(type) { | |
227 case []byte: | |
228 v.SetBytes(x) | |
229 case gae.DSByteString: | |
230 v.SetBytes([]byte(x)) | |
231 default: | |
232 panic(fmt.Errorf("helper: impossible: %s", typeM
ismatchReason(pVal, v))) | |
233 } | |
234 default: | |
235 panic(fmt.Errorf("helper: impossible: %s", typeMismatchR
eason(pVal, v))) | |
236 } | |
237 } | |
238 if slice.IsValid() { | |
239 slice.Set(reflect.Append(slice, v)) | |
240 } | |
241 return "" | |
242 } | |
243 | |
244 func (p *structPLS) Save(withMeta bool) (gae.DSPropertyMap, error) { | |
245 size := len(p.c.byName) | |
246 if withMeta { | |
247 size += len(p.c.byMeta) | |
248 } | |
249 ret := make(gae.DSPropertyMap, size) | |
250 if _, err := p.save(ret, "", gae.ShouldIndex); err != nil { | |
251 return nil, err | |
252 } | |
253 if withMeta { | |
254 for k := range p.c.byMeta { | |
255 val, err := p.GetMeta(k) | |
256 if err != nil { | |
257 return nil, err // TODO(riannucci): should these
be ignored? | |
258 } | |
259 p := gae.DSProperty{} | |
260 if err = p.SetValue(val, gae.NoIndex); err != nil { | |
261 return nil, err | |
262 } | |
263 ret["$"+k] = []gae.DSProperty{p} | |
264 } | |
265 } | |
266 return ret, nil | |
267 } | |
268 | |
269 func (p *structPLS) save(propMap gae.DSPropertyMap, prefix string, is gae.IndexS
etting) (idxCount int, err error) { | |
270 if err = p.Problem(); err != nil { | |
271 return | |
272 } | |
273 | |
274 saveProp := func(name string, si gae.IndexSetting, v reflect.Value, st *
structTag) (err error) { | |
275 if st.substructCodec != nil { | |
276 count, err := (&structPLS{v, st.substructCodec}).save(pr
opMap, name, si) | |
277 if err == nil { | |
278 idxCount += count | |
279 if idxCount > maxIndexedProperties { | |
280 err = errors.New("gae: too many indexed
properties") | |
281 } | |
282 } | |
283 return err | |
284 } | |
285 | |
286 prop := gae.DSProperty{} | |
287 if st.convert { | |
288 prop, err = v.Addr().Interface().(gae.DSPropertyConverte
r).ToDSProperty() | |
289 } else { | |
290 err = prop.SetValue(v.Interface(), si) | |
291 } | |
292 if err != nil { | |
293 return err | |
294 } | |
295 propMap[name] = append(propMap[name], prop) | |
296 if prop.IndexSetting() == gae.ShouldIndex { | |
297 idxCount++ | |
298 if idxCount > maxIndexedProperties { | |
299 return errors.New("gae: too many indexed propert
ies") | |
300 } | |
301 } | |
302 return nil | |
303 } | |
304 | |
305 for i, st := range p.c.byIndex { | |
306 if st.name == "-" { | |
307 continue | |
308 } | |
309 name := st.name | |
310 if prefix != "" { | |
311 name = prefix + name | |
312 } | |
313 v := p.o.Field(i) | |
314 is1 := is | |
315 if st.idxSetting == gae.NoIndex { | |
316 is1 = gae.NoIndex | |
317 } | |
318 if st.isSlice { | |
319 for j := 0; j < v.Len(); j++ { | |
320 if err = saveProp(name, is1, v.Index(j), &st); e
rr != nil { | |
321 return | |
322 } | |
323 } | |
324 } else { | |
325 if err = saveProp(name, is1, v, &st); err != nil { | |
326 return | |
327 } | |
328 } | |
329 } | |
330 return | |
331 } | |
332 | |
333 func (p *structPLS) GetMeta(key string) (interface{}, error) { | |
334 if err := p.Problem(); err != nil { | |
335 return nil, err | |
336 } | |
337 idx, ok := p.c.byMeta[key] | |
338 if !ok { | |
339 return nil, gae.ErrDSMetaFieldUnset | |
340 } | |
341 st := p.c.byIndex[idx] | |
342 val := st.metaVal | |
343 f := p.o.Field(idx) | |
344 if st.canSet { | |
345 if !reflect.DeepEqual(reflect.Zero(f.Type()).Interface(), f.Inte
rface()) { | |
346 val = f.Interface() | |
347 } | |
348 } | |
349 return val, nil | |
350 } | |
351 | |
352 func (p *structPLS) SetMeta(key string, val interface{}) (err error) { | |
353 if err = p.Problem(); err != nil { | |
354 return | |
355 } | |
356 idx, ok := p.c.byMeta[key] | |
357 if !ok { | |
358 return gae.ErrDSMetaFieldUnset | |
359 } | |
360 if !p.c.byIndex[idx].canSet { | |
361 return fmt.Errorf("gae/helper: cannot set meta %q: unexported fi
eld", key) | |
362 } | |
363 p.o.Field(idx).Set(reflect.ValueOf(val)) | |
364 return nil | |
365 } | |
366 | |
367 func (p *structPLS) Problem() error { return p.c.problem } | |
368 | |
369 var ( | |
370 // The RWMutex is chosen intentionally, as the majority of access to the | |
371 // structCodecs map will be in parallel and will be to read an existing
codec. | |
372 // There's no reason to serialize goroutines on every | |
373 // gae.RawDatastore.{Get,Put}{,Multi} call. | |
374 structCodecsMutex sync.RWMutex | |
375 structCodecs = map[reflect.Type]*structCodec{} | |
376 ) | |
377 | |
378 // validPropertyName returns whether name consists of one or more valid Go | |
379 // identifiers joined by ".". | |
380 func validPropertyName(name string) bool { | |
381 if name == "" { | |
382 return false | |
383 } | |
384 for _, s := range strings.Split(name, ".") { | |
385 if s == "" { | |
386 return false | |
387 } | |
388 first := true | |
389 for _, c := range s { | |
390 if first { | |
391 first = false | |
392 if c != '_' && !unicode.IsLetter(c) { | |
393 return false | |
394 } | |
395 } else { | |
396 if c != '_' && !unicode.IsLetter(c) && !unicode.
IsDigit(c) { | |
397 return false | |
398 } | |
399 } | |
400 } | |
401 } | |
402 return true | |
403 } | |
404 | |
405 var ( | |
406 errRecursiveStruct = fmt.Errorf("(internal): struct type is recursively
defined") | |
407 ) | |
408 | |
409 func getStructCodecLocked(t reflect.Type) (c *structCodec) { | |
410 if c, ok := structCodecs[t]; ok { | |
411 return c | |
412 } | |
413 | |
414 me := func(fmtStr string, args ...interface{}) error { | |
415 return fmt.Errorf(fmtStr, args...) | |
416 } | |
417 | |
418 c = &structCodec{ | |
419 byIndex: make([]structTag, t.NumField()), | |
420 byName: make(map[string]int, t.NumField()), | |
421 byMeta: make(map[string]int, t.NumField()), | |
422 problem: errRecursiveStruct, // we'll clear this later if it's n
ot recursive | |
423 } | |
424 defer func() { | |
425 // If the codec has a problem, free up the indexes | |
426 if c.problem != nil { | |
427 c.byIndex = nil | |
428 c.byName = nil | |
429 c.byMeta = nil | |
430 } | |
431 }() | |
432 structCodecs[t] = c | |
433 | |
434 for i := range c.byIndex { | |
435 st := &c.byIndex[i] | |
436 f := t.Field(i) | |
437 name := f.Tag.Get("gae") | |
438 opts := "" | |
439 if i := strings.Index(name, ","); i != -1 { | |
440 name, opts = name[:i], name[i+1:] | |
441 } | |
442 st.canSet = f.PkgPath == "" // blank == exported | |
443 switch { | |
444 case name == "": | |
445 if !f.Anonymous { | |
446 name = f.Name | |
447 } | |
448 case name[0] == '$': | |
449 name = name[1:] | |
450 if _, ok := c.byMeta[name]; ok { | |
451 c.problem = me("meta field %q set multiple times
", "$"+name) | |
452 return | |
453 } | |
454 c.byMeta[name] = i | |
455 mv, err := convertMeta(opts, f.Type) | |
456 if err != nil { | |
457 c.problem = me("meta field %q has bad type: %s",
"$"+name, err) | |
458 return | |
459 } | |
460 st.metaVal = mv | |
461 fallthrough | |
462 case name == "-": | |
463 st.name = "-" | |
464 continue | |
465 default: | |
466 if !validPropertyName(name) { | |
467 c.problem = me("struct tag has invalid property
name: %q", name) | |
468 return | |
469 } | |
470 } | |
471 if !st.canSet { | |
472 st.name = "-" | |
473 continue | |
474 } | |
475 | |
476 substructType := reflect.Type(nil) | |
477 ft := f.Type | |
478 if reflect.PtrTo(ft).Implements(typeOfDSPropertyConverter) { | |
479 st.convert = true | |
480 } else { | |
481 switch f.Type.Kind() { | |
482 case reflect.Struct: | |
483 if ft != typeOfTime && ft != typeOfGeoPoint { | |
484 substructType = ft | |
485 } | |
486 case reflect.Slice: | |
487 if reflect.PtrTo(ft.Elem()).Implements(typeOfDSP
ropertyConverter) { | |
488 st.convert = true | |
489 } else if ft.Elem().Kind() == reflect.Struct { | |
490 substructType = ft.Elem() | |
491 } | |
492 st.isSlice = ft.Elem().Kind() != reflect.Uint8 | |
493 c.hasSlice = c.hasSlice || st.isSlice | |
494 case reflect.Interface: | |
495 if ft != typeOfDSKey { | |
496 c.problem = me("field %q has non-concret
e interface type %s", | |
497 f.Name, f.Type) | |
498 return | |
499 } | |
500 } | |
501 } | |
502 | |
503 if substructType != nil { | |
504 sub := getStructCodecLocked(substructType) | |
505 if sub.problem != nil { | |
506 if sub.problem == errRecursiveStruct { | |
507 c.problem = me("field %q is recursively
defined", f.Name) | |
508 } else { | |
509 c.problem = me("field %q has problem: %s
", f.Name, sub.problem) | |
510 } | |
511 return | |
512 } | |
513 st.substructCodec = sub | |
514 if st.isSlice && sub.hasSlice { | |
515 c.problem = me( | |
516 "flattening nested structs leads to a sl
ice of slices: field %q", | |
517 f.Name) | |
518 return | |
519 } | |
520 c.hasSlice = c.hasSlice || sub.hasSlice | |
521 if name != "" { | |
522 name += "." | |
523 } | |
524 for relName := range sub.byName { | |
525 absName := name + relName | |
526 if _, ok := c.byName[absName]; ok { | |
527 c.problem = me("struct tag has repeated
property name: %q", absName) | |
528 return | |
529 } | |
530 c.byName[absName] = i | |
531 } | |
532 } else { | |
533 if !st.convert { // check the underlying static type of
the field | |
534 t := ft | |
535 if st.isSlice { | |
536 t = t.Elem() | |
537 } | |
538 v := reflect.New(t).Elem().Interface() | |
539 v, _ = gae.DSUpconvertUnderlyingType(v, t) | |
540 if _, err := gae.DSPropertyTypeOf(v, false); err
!= nil { | |
541 c.problem = me("field %q has invalid typ
e: %s", name, ft) | |
542 return | |
543 } | |
544 } | |
545 | |
546 if _, ok := c.byName[name]; ok { | |
547 c.problem = me("struct tag has repeated property
name: %q", name) | |
548 return | |
549 } | |
550 c.byName[name] = i | |
551 } | |
552 st.name = name | |
553 if opts == "noindex" { | |
554 st.idxSetting = gae.NoIndex | |
555 } | |
556 } | |
557 if c.problem == errRecursiveStruct { | |
558 c.problem = nil | |
559 } | |
560 return | |
561 } | |
562 | |
563 func convertMeta(val string, t reflect.Type) (interface{}, error) { | |
564 switch t { | |
565 case typeOfString: | |
566 return val, nil | |
567 case typeOfInt64: | |
568 if val == "" { | |
569 return int64(0), nil | |
570 } | |
571 return strconv.ParseInt(val, 10, 64) | |
572 } | |
573 return nil, fmt.Errorf("helper: meta field with bad type/value %s/%s", t
, val) | |
574 } | |
OLD | NEW |