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 package datastore | 5 package datastore |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | |
9 "encoding/base64" | |
8 "errors" | 10 "errors" |
9 "fmt" | 11 "fmt" |
10 "math" | 12 "math" |
11 "reflect" | 13 "reflect" |
12 "time" | 14 "time" |
13 | 15 |
14 "github.com/luci/gae/service/blobstore" | 16 "github.com/luci/gae/service/blobstore" |
15 ) | 17 ) |
16 | 18 |
17 var ( | 19 var ( |
(...skipping 14 matching lines...) Expand all Loading... | |
32 NoIndex IndexSetting = true | 34 NoIndex IndexSetting = true |
33 ) | 35 ) |
34 | 36 |
35 func (i IndexSetting) String() string { | 37 func (i IndexSetting) String() string { |
36 if i { | 38 if i { |
37 return "NoIndex" | 39 return "NoIndex" |
38 } | 40 } |
39 return "ShouldIndex" | 41 return "ShouldIndex" |
40 } | 42 } |
41 | 43 |
42 // Property is a value plus an indicator of whether the value should be | |
43 // indexed. Name and Multiple are stored in the PropertyMap object. | |
44 type Property struct { | |
45 value interface{} | |
46 indexSetting IndexSetting | |
47 propType PropertyType | |
48 } | |
49 | |
50 // MkProperty makes a new indexed* Property and returns it. If val is an | |
51 // invalid value, this panics (so don't do it). If you want to handle the error | |
52 // normally, use SetValue(..., ShouldIndex) instead. | |
53 // | |
54 // *indexed if val is not an unindexable type like []byte. | |
55 func MkProperty(val interface{}) Property { | |
56 ret := Property{} | |
57 if err := ret.SetValue(val, ShouldIndex); err != nil { | |
58 panic(err) | |
59 } | |
60 return ret | |
61 } | |
62 | |
63 // MkPropertyNI makes a new Property (with noindex set to true), and returns | |
64 // it. If val is an invalid value, this panics (so don't do it). If you want to | |
65 // handle the error normally, use SetValue(..., NoIndex) instead. | |
66 func MkPropertyNI(val interface{}) Property { | |
67 ret := Property{} | |
68 if err := ret.SetValue(val, NoIndex); err != nil { | |
69 panic(err) | |
70 } | |
71 return ret | |
72 } | |
73 | |
74 // PropertyConverter may be implemented by the pointer-to a struct field which | 44 // PropertyConverter may be implemented by the pointer-to a struct field which |
75 // is serialized by the struct PropertyLoadSaver from GetPLS. Its ToProperty | 45 // is serialized by the struct PropertyLoadSaver from GetPLS. Its ToProperty |
76 // will be called on save, and it's FromProperty will be called on load (from | 46 // will be called on save, and it's FromProperty will be called on load (from |
77 // datastore). The method may do arbitrary computation, and if it encounters an | 47 // datastore). The method may do arbitrary computation, and if it encounters an |
78 // error, may return it. This error will be a fatal error (as defined by | 48 // error, may return it. This error will be a fatal error (as defined by |
79 // PropertyLoadSaver) for the struct conversion. | 49 // PropertyLoadSaver) for the struct conversion. |
80 // | 50 // |
81 // Example: | 51 // Example: |
82 // type Complex complex | 52 // type Complex complex |
83 // func (c *Complex) ToProperty() (ret Property, err error) { | 53 // func (c *Complex) ToProperty() (ret Property, err error) { |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
169 PTString | 139 PTString |
170 | 140 |
171 // PTFloat is always a float64. | 141 // PTFloat is always a float64. |
172 // | 142 // |
173 // This is a Projection-query type. | 143 // This is a Projection-query type. |
174 PTFloat | 144 PTFloat |
175 | 145 |
176 // PTGeoPoint is a Projection-query type. | 146 // PTGeoPoint is a Projection-query type. |
177 PTGeoPoint | 147 PTGeoPoint |
178 | 148 |
179 » // PTKey represents a Key object. | 149 » // PTKey represents a *Key object. |
180 // | 150 // |
181 // PTKey is a Projection-query type. | 151 // PTKey is a Projection-query type. |
182 PTKey | 152 PTKey |
183 | 153 |
184 // PTBlobKey represents a blobstore.Key | 154 // PTBlobKey represents a blobstore.Key |
185 PTBlobKey | 155 PTBlobKey |
186 | 156 |
187 // NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK. | 157 // NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK. |
188 PTUnknown | 158 PTUnknown |
189 ) | 159 ) |
(...skipping 28 matching lines...) Expand all Loading... | |
218 return "PTGeoPoint" | 188 return "PTGeoPoint" |
219 case PTKey: | 189 case PTKey: |
220 return "PTKey" | 190 return "PTKey" |
221 case PTBlobKey: | 191 case PTBlobKey: |
222 return "PTBlobKey" | 192 return "PTBlobKey" |
223 default: | 193 default: |
224 return fmt.Sprintf("PTUnknown(%02x)", byte(t)) | 194 return fmt.Sprintf("PTUnknown(%02x)", byte(t)) |
225 } | 195 } |
226 } | 196 } |
227 | 197 |
198 // Property is a value plus an indicator of whether the value should be | |
199 // indexed. Name and Multiple are stored in the PropertyMap object. | |
200 type Property struct { | |
201 value interface{} | |
202 indexSetting IndexSetting | |
203 propType PropertyType | |
204 } | |
205 | |
206 // MkProperty makes a new indexed* Property and returns it. If val is an | |
207 // invalid value, this panics (so don't do it). If you want to handle the error | |
208 // normally, use SetValue(..., ShouldIndex) instead. | |
209 // | |
210 // *indexed if val is not an unindexable type like []byte. | |
211 func MkProperty(val interface{}) Property { | |
dnj
2015/09/18 16:47:58
Now that Property is a struct, this should probabl
iannucci
2015/09/18 22:25:48
It has always been a struct. I chose not to pass *
| |
212 ret := Property{} | |
213 if err := ret.SetValue(val, ShouldIndex); err != nil { | |
214 panic(err) | |
215 } | |
216 return ret | |
217 } | |
218 | |
219 // MkPropertyNI makes a new Property (with noindex set to true), and returns | |
220 // it. If val is an invalid value, this panics (so don't do it). If you want to | |
221 // handle the error normally, use SetValue(..., NoIndex) instead. | |
222 func MkPropertyNI(val interface{}) Property { | |
223 ret := Property{} | |
224 if err := ret.SetValue(val, NoIndex); err != nil { | |
225 panic(err) | |
226 } | |
227 return ret | |
228 } | |
229 | |
228 // PropertyTypeOf returns the PT* type of the given Property-compatible | 230 // PropertyTypeOf returns the PT* type of the given Property-compatible |
229 // value v. If checkValid is true, this method will also ensure that time.Time | 231 // value v. If checkValid is true, this method will also ensure that time.Time |
230 // and GeoPoint have valid values. | 232 // and GeoPoint have valid values. |
231 func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) { | 233 func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) { |
232 switch x := v.(type) { | 234 switch x := v.(type) { |
233 case nil: | 235 case nil: |
234 return PTNull, nil | 236 return PTNull, nil |
235 case int64: | 237 case int64: |
236 return PTInt, nil | 238 return PTInt, nil |
237 case float64: | 239 case float64: |
238 return PTFloat, nil | 240 return PTFloat, nil |
239 case bool: | 241 case bool: |
240 return PTBool, nil | 242 return PTBool, nil |
241 case []byte: | 243 case []byte: |
242 return PTBytes, nil | 244 return PTBytes, nil |
243 case blobstore.Key: | 245 case blobstore.Key: |
244 return PTBlobKey, nil | 246 return PTBlobKey, nil |
245 case string: | 247 case string: |
246 return PTString, nil | 248 return PTString, nil |
247 » case Key: | 249 » case *Key: |
248 // TODO(riannucci): Check key for validity in its own namespace? | 250 // TODO(riannucci): Check key for validity in its own namespace? |
249 return PTKey, nil | 251 return PTKey, nil |
250 case time.Time: | 252 case time.Time: |
251 err := error(nil) | 253 err := error(nil) |
252 if checkValid && (x.Before(minTime) || x.After(maxTime)) { | 254 if checkValid && (x.Before(minTime) || x.After(maxTime)) { |
253 err = errors.New("time value out of range") | 255 err = errors.New("time value out of range") |
254 } | 256 } |
255 if checkValid && !timeLocationIsUTC(x.Location()) { | 257 if checkValid && !timeLocationIsUTC(x.Location()) { |
256 err = fmt.Errorf("time value has wrong Location: %v", x. Location()) | 258 err = fmt.Errorf("time value has wrong Location: %v", x. Location()) |
257 } | 259 } |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
331 // - int64 | 333 // - int64 |
332 // - time.Time | 334 // - time.Time |
333 // - bool | 335 // - bool |
334 // - string | 336 // - string |
335 // (only the first 1500 bytes is indexable) | 337 // (only the first 1500 bytes is indexable) |
336 // - []byte | 338 // - []byte |
337 // (only the first 1500 bytes is indexable) | 339 // (only the first 1500 bytes is indexable) |
338 // - blobstore.Key | 340 // - blobstore.Key |
339 // (only the first 1500 bytes is indexable) | 341 // (only the first 1500 bytes is indexable) |
340 // - float64 | 342 // - float64 |
341 //» - Key | 343 //» - *Key |
342 // - GeoPoint | 344 // - GeoPoint |
343 // This set is smaller than the set of valid struct field types that the | 345 // This set is smaller than the set of valid struct field types that the |
344 // datastore can load and save. A Property Value cannot be a slice (apart | 346 // datastore can load and save. A Property Value cannot be a slice (apart |
345 // from []byte); use multiple Properties instead. Also, a Value's type | 347 // from []byte); use multiple Properties instead. Also, a Value's type |
346 // must be explicitly on the list above; it is not sufficient for the | 348 // must be explicitly on the list above; it is not sufficient for the |
347 // underlying type to be on that list. For example, a Value of "type | 349 // underlying type to be on that list. For example, a Value of "type |
348 // myInt64 int64" is invalid. Smaller-width integers and floats are also | 350 // myInt64 int64" is invalid. Smaller-width integers and floats are also |
349 // invalid. Again, this is more restrictive than the set of valid struct | 351 // invalid. Again, this is more restrictive than the set of valid struct |
350 // field types. | 352 // field types. |
351 // | 353 // |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
443 return nil, nil | 445 return nil, nil |
444 case PTBlobKey: | 446 case PTBlobKey: |
445 return blobstore.Key(""), nil | 447 return blobstore.Key(""), nil |
446 } | 448 } |
447 fallthrough | 449 fallthrough |
448 default: | 450 default: |
449 return nil, fmt.Errorf("unable to project %s to %s", p.propType, to) | 451 return nil, fmt.Errorf("unable to project %s to %s", p.propType, to) |
450 } | 452 } |
451 } | 453 } |
452 | 454 |
455 func cmpVals(a, b interface{}, t PropertyType) int { | |
iannucci
2015/09/18 04:31:53
does anyone know how to write this thing better? u
dnj
2015/09/18 16:47:58
*shrug* inner switch statements could make it a li
| |
456 switch t { | |
457 case PTNull: | |
458 return 0 | |
459 | |
460 case PTBool: | |
461 a, b := a.(bool), b.(bool) | |
462 if a == b { | |
463 return 0 | |
464 } | |
465 if a && !b { | |
466 return 1 | |
467 } | |
468 return -1 | |
469 | |
470 case PTInt: | |
471 a, b := a.(int64), b.(int64) | |
472 if a == b { | |
473 return 0 | |
474 } | |
475 if a > b { | |
476 return 1 | |
477 } | |
478 return -1 | |
479 | |
480 case PTString: | |
481 a, b := a.(string), b.(string) | |
482 if a == b { | |
dnj
2015/09/18 16:47:58
Use strings.Compare here.
| |
483 return 0 | |
484 } | |
485 if a > b { | |
486 return 1 | |
487 } | |
488 return -1 | |
489 | |
490 case PTFloat: | |
491 a, b := a.(float64), b.(float64) | |
492 if a == b { | |
493 return 0 | |
494 } | |
495 if a > b { | |
496 return 1 | |
497 } | |
498 return -1 | |
499 | |
500 case PTGeoPoint: | |
501 a, b := a.(GeoPoint), b.(GeoPoint) | |
502 cmp := cmpVals(a.Lat, b.Lat, PTFloat) | |
dnj
2015/09/18 16:47:58
This seems unnecessarily reflective.
iannucci
2015/09/18 22:25:48
functionized
| |
503 if cmp != 0 { | |
504 return cmp | |
505 } | |
506 return cmpVals(a.Lng, b.Lng, PTFloat) | |
507 | |
508 case PTKey: | |
509 a, b := a.(*Key), b.(*Key) | |
510 if a.Equal(b) { | |
511 return 0 | |
512 } | |
513 if b.Less(a) { | |
514 return 1 | |
515 } | |
516 return -1 | |
517 | |
518 default: | |
519 panic(fmt.Errorf("uncomparable type: %s", t)) | |
520 } | |
521 } | |
522 | |
523 func (p *Property) Less(other *Property) bool { | |
524 if p.indexSetting && !other.indexSetting { | |
525 return true | |
526 } else if !p.indexSetting && other.indexSetting { | |
527 return false | |
528 } | |
529 a, b := p.ForIndex(), other.ForIndex() | |
530 cmp := int(a.propType) - int(b.propType) | |
531 if cmp < 0 { | |
532 return true | |
533 } else if cmp > 0 { | |
534 return false | |
535 } | |
536 return cmpVals(a.value, b.value, a.propType) < 0 | |
537 } | |
538 | |
539 func (p *Property) Equal(other *Property) bool { | |
540 ret := p.indexSetting == other.indexSetting | |
541 if ret { | |
542 a, b := p.ForIndex(), other.ForIndex() | |
543 ret = a.propType == b.propType && cmpVals(a.value, b.value, a.pr opType) == 0 | |
544 } | |
545 return ret | |
546 } | |
547 | |
548 func (p *Property) GQL() string { | |
549 switch p.propType { | |
550 case PTNull: | |
551 return "NULL" | |
552 | |
553 case PTInt, PTFloat, PTBool: | |
554 return fmt.Sprint(p.value) | |
555 | |
556 case PTString: | |
557 return gqlQuoteString(p.value.(string)) | |
558 | |
559 case PTBytes: | |
560 return fmt.Sprintf("BLOB(%q)", | |
561 base64.URLEncoding.EncodeToString(p.value.([]byte))) | |
562 | |
563 case PTBlobKey: | |
564 return fmt.Sprintf("BLOBKEY(%s)", gqlQuoteString( | |
565 string(p.value.(blobstore.Key)))) | |
566 | |
567 case PTKey: | |
568 aid, ns, toks := p.value.(*Key).Split() | |
569 ret := &bytes.Buffer{} | |
570 fmt.Fprintf(ret, "KEY(DATASET(%s)", gqlQuoteString(aid)) | |
571 if ns != "" { | |
572 fmt.Fprintf(ret, ", NAMESPACE(%s)", gqlQuoteString(ns)) | |
573 } | |
574 for _, t := range toks { | |
575 if t.IntID != 0 { | |
576 fmt.Fprintf(ret, ", %s, %d", gqlQuoteString(t.Ki nd), t.IntID) | |
577 } else { | |
578 fmt.Fprintf(ret, ", %s, %s", gqlQuoteString(t.Ki nd), gqlQuoteString(t.StringID)) | |
579 } | |
580 } | |
581 ret.WriteString(")") | |
582 return ret.String() | |
583 | |
584 case PTTime: | |
585 return fmt.Sprintf("DATETIME(%s)", p.value.(time.Time).Format("2 006-01-02T15:04:05.000000Z")) | |
dnj
2015/09/18 16:47:58
time.RFC3339?
iannucci
2015/09/18 22:25:48
Nano, and thanks
| |
586 | |
587 case PTGeoPoint: | |
588 // note that cloud SQL doesn't support this yet, but take a good guess at | |
589 // it. | |
590 v := p.value.(GeoPoint) | |
591 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng) | |
592 } | |
593 panic(fmt.Errorf("bad type: %s", p.propType)) | |
594 } | |
595 | |
596 type PropertySlice []Property | |
597 | |
598 func (s PropertySlice) Len() int { return len(s) } | |
599 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
600 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) } | |
601 | |
453 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to | 602 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to |
454 // abstract the meta argument for RawInterface.GetMulti. | 603 // abstract the meta argument for RawInterface.GetMulti. |
455 type MetaGetter interface { | 604 type MetaGetter interface { |
456 // GetMeta will get information about the field which has the struct tag in | 605 // GetMeta will get information about the field which has the struct tag in |
457 // the form of `gae:"$<key>[,<default>]?"`. | 606 // the form of `gae:"$<key>[,<default>]?"`. |
458 // | 607 // |
459 // Supported metadata types are: | 608 // Supported metadata types are: |
460 // int64 - may have default (ascii encoded base-10) | 609 // int64 - may have default (ascii encoded base-10) |
461 // string - may have default | 610 // string - may have default |
462 // Toggle - MUST have default ("true" or "false") | 611 // Toggle - MUST have default ("true" or "false") |
463 » // Key - NO default allowed | 612 » // *Key - NO default allowed |
464 // | 613 // |
465 // Struct fields of type Toggle (which is an Auto/On/Off) require you to | 614 // Struct fields of type Toggle (which is an Auto/On/Off) require you to |
466 // specify a value of 'true' or 'false' for the default value of the str uct | 615 // specify a value of 'true' or 'false' for the default value of the str uct |
467 // tag, and GetMeta will return the combined value as a regular boolean true | 616 // tag, and GetMeta will return the combined value as a regular boolean true |
468 // or false value. | 617 // or false value. |
469 // Example: | 618 // Example: |
470 // type MyStruct struct { | 619 // type MyStruct struct { |
471 // CoolField int64 `gae:"$id,1"` | 620 // CoolField int64 `gae:"$id,1"` |
472 // } | 621 // } |
473 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") | 622 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
624 dflt = UpconvertUnderlyingType(dflt) | 773 dflt = UpconvertUnderlyingType(dflt) |
625 cur, err := gm(key) | 774 cur, err := gm(key) |
626 if err != nil { | 775 if err != nil { |
627 return dflt | 776 return dflt |
628 } | 777 } |
629 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) { | 778 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) { |
630 return dflt | 779 return dflt |
631 } | 780 } |
632 return cur | 781 return cur |
633 } | 782 } |
OLD | NEW |