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 "encoding/base64" |
8 "errors" | 9 "errors" |
9 "fmt" | 10 "fmt" |
10 "math" | 11 "math" |
11 "reflect" | 12 "reflect" |
12 "time" | 13 "time" |
13 | 14 |
14 "github.com/luci/gae/service/blobstore" | 15 "github.com/luci/gae/service/blobstore" |
15 ) | 16 ) |
16 | 17 |
17 var ( | 18 var ( |
(...skipping 14 matching lines...) Expand all Loading... |
32 NoIndex IndexSetting = true | 33 NoIndex IndexSetting = true |
33 ) | 34 ) |
34 | 35 |
35 func (i IndexSetting) String() string { | 36 func (i IndexSetting) String() string { |
36 if i { | 37 if i { |
37 return "NoIndex" | 38 return "NoIndex" |
38 } | 39 } |
39 return "ShouldIndex" | 40 return "ShouldIndex" |
40 } | 41 } |
41 | 42 |
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 | 43 // PropertyConverter may be implemented by the pointer-to a struct field which |
75 // is serialized by the struct PropertyLoadSaver from GetPLS. Its ToProperty | 44 // 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 | 45 // 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 | 46 // 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 | 47 // error, may return it. This error will be a fatal error (as defined by |
79 // PropertyLoadSaver) for the struct conversion. | 48 // PropertyLoadSaver) for the struct conversion. |
80 // | 49 // |
81 // Example: | 50 // Example: |
82 // type Complex complex | 51 // type Complex complex |
83 // func (c *Complex) ToProperty() (ret Property, err error) { | 52 // func (c *Complex) ToProperty() (ret Property, err error) { |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
169 PTString | 138 PTString |
170 | 139 |
171 // PTFloat is always a float64. | 140 // PTFloat is always a float64. |
172 // | 141 // |
173 // This is a Projection-query type. | 142 // This is a Projection-query type. |
174 PTFloat | 143 PTFloat |
175 | 144 |
176 // PTGeoPoint is a Projection-query type. | 145 // PTGeoPoint is a Projection-query type. |
177 PTGeoPoint | 146 PTGeoPoint |
178 | 147 |
179 » // PTKey represents a Key object. | 148 » // PTKey represents a *Key object. |
180 // | 149 // |
181 // PTKey is a Projection-query type. | 150 // PTKey is a Projection-query type. |
182 PTKey | 151 PTKey |
183 | 152 |
184 // PTBlobKey represents a blobstore.Key | 153 // PTBlobKey represents a blobstore.Key |
185 PTBlobKey | 154 PTBlobKey |
186 | 155 |
| 156 // PTUnknown is a placeholder value which should never show up in realit
y. |
| 157 // |
187 // NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK. | 158 // NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK. |
188 PTUnknown | 159 PTUnknown |
189 ) | 160 ) |
190 | 161 |
191 func init() { | 162 func init() { |
192 if PTUnknown > 0x7e { | 163 if PTUnknown > 0x7e { |
193 panic( | 164 panic( |
194 "PTUnknown (and therefore PropertyType) exceeds 0x7e. Th
is conflicts " + | 165 "PTUnknown (and therefore PropertyType) exceeds 0x7e. Th
is conflicts " + |
195 "with serialize.WriteProperty's use of the high
bit to indicate " + | 166 "with serialize.WriteProperty's use of the high
bit to indicate " + |
196 "NoIndex and/or \"impl/memory\".increment's abil
ity to guarantee " + | 167 "NoIndex and/or \"impl/memory\".increment's abil
ity to guarantee " + |
(...skipping 21 matching lines...) Expand all Loading... |
218 return "PTGeoPoint" | 189 return "PTGeoPoint" |
219 case PTKey: | 190 case PTKey: |
220 return "PTKey" | 191 return "PTKey" |
221 case PTBlobKey: | 192 case PTBlobKey: |
222 return "PTBlobKey" | 193 return "PTBlobKey" |
223 default: | 194 default: |
224 return fmt.Sprintf("PTUnknown(%02x)", byte(t)) | 195 return fmt.Sprintf("PTUnknown(%02x)", byte(t)) |
225 } | 196 } |
226 } | 197 } |
227 | 198 |
| 199 // Property is a value plus an indicator of whether the value should be |
| 200 // indexed. Name and Multiple are stored in the PropertyMap object. |
| 201 type Property struct { |
| 202 value interface{} |
| 203 indexSetting IndexSetting |
| 204 propType PropertyType |
| 205 } |
| 206 |
| 207 // MkProperty makes a new indexed* Property and returns it. If val is an |
| 208 // invalid value, this panics (so don't do it). If you want to handle the error |
| 209 // normally, use SetValue(..., ShouldIndex) instead. |
| 210 // |
| 211 // *indexed if val is not an unindexable type like []byte. |
| 212 func MkProperty(val interface{}) Property { |
| 213 ret := Property{} |
| 214 if err := ret.SetValue(val, ShouldIndex); err != nil { |
| 215 panic(err) |
| 216 } |
| 217 return ret |
| 218 } |
| 219 |
| 220 // MkPropertyNI makes a new Property (with noindex set to true), and returns |
| 221 // it. If val is an invalid value, this panics (so don't do it). If you want to |
| 222 // handle the error normally, use SetValue(..., NoIndex) instead. |
| 223 func MkPropertyNI(val interface{}) Property { |
| 224 ret := Property{} |
| 225 if err := ret.SetValue(val, NoIndex); err != nil { |
| 226 panic(err) |
| 227 } |
| 228 return ret |
| 229 } |
| 230 |
228 // PropertyTypeOf returns the PT* type of the given Property-compatible | 231 // 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 | 232 // value v. If checkValid is true, this method will also ensure that time.Time |
230 // and GeoPoint have valid values. | 233 // and GeoPoint have valid values. |
231 func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) { | 234 func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) { |
232 switch x := v.(type) { | 235 switch x := v.(type) { |
233 case nil: | 236 case nil: |
234 return PTNull, nil | 237 return PTNull, nil |
235 case int64: | 238 case int64: |
236 return PTInt, nil | 239 return PTInt, nil |
237 case float64: | 240 case float64: |
238 return PTFloat, nil | 241 return PTFloat, nil |
239 case bool: | 242 case bool: |
240 return PTBool, nil | 243 return PTBool, nil |
241 case []byte: | 244 case []byte: |
242 return PTBytes, nil | 245 return PTBytes, nil |
243 case blobstore.Key: | 246 case blobstore.Key: |
244 return PTBlobKey, nil | 247 return PTBlobKey, nil |
245 case string: | 248 case string: |
246 return PTString, nil | 249 return PTString, nil |
247 » case Key: | 250 » case *Key: |
248 // TODO(riannucci): Check key for validity in its own namespace? | 251 // TODO(riannucci): Check key for validity in its own namespace? |
249 return PTKey, nil | 252 return PTKey, nil |
250 case time.Time: | 253 case time.Time: |
251 err := error(nil) | 254 err := error(nil) |
252 if checkValid && (x.Before(minTime) || x.After(maxTime)) { | 255 if checkValid && (x.Before(minTime) || x.After(maxTime)) { |
253 err = errors.New("time value out of range") | 256 err = errors.New("time value out of range") |
254 } | 257 } |
255 if checkValid && !timeLocationIsUTC(x.Location()) { | 258 if checkValid && !timeLocationIsUTC(x.Location()) { |
256 err = fmt.Errorf("time value has wrong Location: %v", x.
Location()) | 259 err = fmt.Errorf("time value has wrong Location: %v", x.
Location()) |
257 } | 260 } |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
331 // - int64 | 334 // - int64 |
332 // - time.Time | 335 // - time.Time |
333 // - bool | 336 // - bool |
334 // - string | 337 // - string |
335 // (only the first 1500 bytes is indexable) | 338 // (only the first 1500 bytes is indexable) |
336 // - []byte | 339 // - []byte |
337 // (only the first 1500 bytes is indexable) | 340 // (only the first 1500 bytes is indexable) |
338 // - blobstore.Key | 341 // - blobstore.Key |
339 // (only the first 1500 bytes is indexable) | 342 // (only the first 1500 bytes is indexable) |
340 // - float64 | 343 // - float64 |
341 //» - Key | 344 //» - *Key |
342 // - GeoPoint | 345 // - GeoPoint |
343 // This set is smaller than the set of valid struct field types that the | 346 // 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 | 347 // 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 | 348 // 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 | 349 // 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 | 350 // 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 | 351 // myInt64 int64" is invalid. Smaller-width integers and floats are also |
349 // invalid. Again, this is more restrictive than the set of valid struct | 352 // invalid. Again, this is more restrictive than the set of valid struct |
350 // field types. | 353 // field types. |
351 // | 354 // |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
443 return nil, nil | 446 return nil, nil |
444 case PTBlobKey: | 447 case PTBlobKey: |
445 return blobstore.Key(""), nil | 448 return blobstore.Key(""), nil |
446 } | 449 } |
447 fallthrough | 450 fallthrough |
448 default: | 451 default: |
449 return nil, fmt.Errorf("unable to project %s to %s", p.propType,
to) | 452 return nil, fmt.Errorf("unable to project %s to %s", p.propType,
to) |
450 } | 453 } |
451 } | 454 } |
452 | 455 |
| 456 func cmpVals(a, b interface{}, t PropertyType) int { |
| 457 cmpFloat := func(a, b float64) int { |
| 458 if a == b { |
| 459 return 0 |
| 460 } |
| 461 if a > b { |
| 462 return 1 |
| 463 } |
| 464 return -1 |
| 465 } |
| 466 |
| 467 switch t { |
| 468 case PTNull: |
| 469 return 0 |
| 470 |
| 471 case PTBool: |
| 472 a, b := a.(bool), b.(bool) |
| 473 if a == b { |
| 474 return 0 |
| 475 } |
| 476 if a && !b { |
| 477 return 1 |
| 478 } |
| 479 return -1 |
| 480 |
| 481 case PTInt: |
| 482 a, b := a.(int64), b.(int64) |
| 483 if a == b { |
| 484 return 0 |
| 485 } |
| 486 if a > b { |
| 487 return 1 |
| 488 } |
| 489 return -1 |
| 490 |
| 491 case PTString: |
| 492 a, b := a.(string), b.(string) |
| 493 if a == b { |
| 494 return 0 |
| 495 } |
| 496 if a > b { |
| 497 return 1 |
| 498 } |
| 499 return -1 |
| 500 |
| 501 case PTFloat: |
| 502 return cmpFloat(a.(float64), b.(float64)) |
| 503 |
| 504 case PTGeoPoint: |
| 505 a, b := a.(GeoPoint), b.(GeoPoint) |
| 506 cmp := cmpFloat(a.Lat, b.Lat) |
| 507 if cmp != 0 { |
| 508 return cmp |
| 509 } |
| 510 return cmpFloat(a.Lng, b.Lng) |
| 511 |
| 512 case PTKey: |
| 513 a, b := a.(*Key), b.(*Key) |
| 514 if a.Equal(b) { |
| 515 return 0 |
| 516 } |
| 517 if b.Less(a) { |
| 518 return 1 |
| 519 } |
| 520 return -1 |
| 521 |
| 522 default: |
| 523 panic(fmt.Errorf("uncomparable type: %s", t)) |
| 524 } |
| 525 } |
| 526 |
| 527 // Less returns true iff p would sort before other. |
| 528 // |
| 529 // This uses datastore's index rules for sorting (e.g. |
| 530 // []byte("hello") == "hello") |
| 531 func (p *Property) Less(other *Property) bool { |
| 532 if p.indexSetting && !other.indexSetting { |
| 533 return true |
| 534 } else if !p.indexSetting && other.indexSetting { |
| 535 return false |
| 536 } |
| 537 a, b := p.ForIndex(), other.ForIndex() |
| 538 cmp := int(a.propType) - int(b.propType) |
| 539 if cmp < 0 { |
| 540 return true |
| 541 } else if cmp > 0 { |
| 542 return false |
| 543 } |
| 544 return cmpVals(a.value, b.value, a.propType) < 0 |
| 545 } |
| 546 |
| 547 // Equal returns true iff p and other have identical index representations. |
| 548 // |
| 549 // This uses datastore's index rules for sorting (e.g. |
| 550 // []byte("hello") == "hello") |
| 551 func (p *Property) Equal(other *Property) bool { |
| 552 ret := p.indexSetting == other.indexSetting |
| 553 if ret { |
| 554 a, b := p.ForIndex(), other.ForIndex() |
| 555 ret = a.propType == b.propType && cmpVals(a.value, b.value, a.pr
opType) == 0 |
| 556 } |
| 557 return ret |
| 558 } |
| 559 |
| 560 // GQL returns a correctly formatted Cloud Datastore GQL literal which |
| 561 // is valid for a comparison value in the `WHERE` clause. |
| 562 // |
| 563 // The flavor of GQL that this emits is defined here: |
| 564 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference |
| 565 // |
| 566 // NOTE: GeoPoint values are emitted with speculated future syntax. There is |
| 567 // currently no syntax for literal GeoPoint values. |
| 568 func (p *Property) GQL() string { |
| 569 switch p.propType { |
| 570 case PTNull: |
| 571 return "NULL" |
| 572 |
| 573 case PTInt, PTFloat, PTBool: |
| 574 return fmt.Sprint(p.value) |
| 575 |
| 576 case PTString: |
| 577 return gqlQuoteString(p.value.(string)) |
| 578 |
| 579 case PTBytes: |
| 580 return fmt.Sprintf("BLOB(%q)", |
| 581 base64.URLEncoding.EncodeToString(p.value.([]byte))) |
| 582 |
| 583 case PTBlobKey: |
| 584 return fmt.Sprintf("BLOBKEY(%s)", gqlQuoteString( |
| 585 string(p.value.(blobstore.Key)))) |
| 586 |
| 587 case PTKey: |
| 588 return p.value.(*Key).GQL() |
| 589 |
| 590 case PTTime: |
| 591 return fmt.Sprintf("DATETIME(%s)", p.value.(time.Time).Format(ti
me.RFC3339Nano)) |
| 592 |
| 593 case PTGeoPoint: |
| 594 // note that cloud SQL doesn't support this yet, but take a good
guess at |
| 595 // it. |
| 596 v := p.value.(GeoPoint) |
| 597 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng) |
| 598 } |
| 599 panic(fmt.Errorf("bad type: %s", p.propType)) |
| 600 } |
| 601 |
| 602 // PropertySlice is a slice of Properties. It implements sort.Interface. |
| 603 type PropertySlice []Property |
| 604 |
| 605 func (s PropertySlice) Len() int { return len(s) } |
| 606 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| 607 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) } |
| 608 |
453 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to | 609 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to |
454 // abstract the meta argument for RawInterface.GetMulti. | 610 // abstract the meta argument for RawInterface.GetMulti. |
455 type MetaGetter interface { | 611 type MetaGetter interface { |
456 // GetMeta will get information about the field which has the struct tag
in | 612 // GetMeta will get information about the field which has the struct tag
in |
457 // the form of `gae:"$<key>[,<default>]?"`. | 613 // the form of `gae:"$<key>[,<default>]?"`. |
458 // | 614 // |
459 // Supported metadata types are: | 615 // Supported metadata types are: |
460 // int64 - may have default (ascii encoded base-10) | 616 // int64 - may have default (ascii encoded base-10) |
461 // string - may have default | 617 // string - may have default |
462 // Toggle - MUST have default ("true" or "false") | 618 // Toggle - MUST have default ("true" or "false") |
463 » // Key - NO default allowed | 619 » // *Key - NO default allowed |
464 // | 620 // |
465 // Struct fields of type Toggle (which is an Auto/On/Off) require you to | 621 // 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 | 622 // 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 | 623 // tag, and GetMeta will return the combined value as a regular boolean
true |
468 // or false value. | 624 // or false value. |
469 // Example: | 625 // Example: |
470 // type MyStruct struct { | 626 // type MyStruct struct { |
471 // CoolField int64 `gae:"$id,1"` | 627 // CoolField int64 `gae:"$id,1"` |
472 // } | 628 // } |
473 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") | 629 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
583 v, ok := pm["$"+key] | 739 v, ok := pm["$"+key] |
584 if !ok || len(v) == 0 { | 740 if !ok || len(v) == 0 { |
585 return nil, ErrMetaFieldUnset | 741 return nil, ErrMetaFieldUnset |
586 } | 742 } |
587 if len(v) > 1 { | 743 if len(v) > 1 { |
588 return nil, errors.New("gae: too many values for Meta key") | 744 return nil, errors.New("gae: too many values for Meta key") |
589 } | 745 } |
590 return v[0].Value(), nil | 746 return v[0].Value(), nil |
591 } | 747 } |
592 | 748 |
| 749 // GetMetaDefault is the implementation of PropertyLoadSaver.GetMetaDefault. |
593 func (pm PropertyMap) GetMetaDefault(key string, dflt interface{}) interface{} { | 750 func (pm PropertyMap) GetMetaDefault(key string, dflt interface{}) interface{} { |
594 return GetMetaDefaultImpl(pm.GetMeta, key, dflt) | 751 return GetMetaDefaultImpl(pm.GetMeta, key, dflt) |
595 } | 752 } |
596 | 753 |
597 // SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error | 754 // SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error |
598 // if `val` has an invalid type (e.g. not one supported by Property). | 755 // if `val` has an invalid type (e.g. not one supported by Property). |
599 func (pm PropertyMap) SetMeta(key string, val interface{}) error { | 756 func (pm PropertyMap) SetMeta(key string, val interface{}) error { |
600 prop := Property{} | 757 prop := Property{} |
601 if err := prop.SetValue(val, NoIndex); err != nil { | 758 if err := prop.SetValue(val, NoIndex); err != nil { |
602 return err | 759 return err |
(...skipping 21 matching lines...) Expand all Loading... |
624 dflt = UpconvertUnderlyingType(dflt) | 781 dflt = UpconvertUnderlyingType(dflt) |
625 cur, err := gm(key) | 782 cur, err := gm(key) |
626 if err != nil { | 783 if err != nil { |
627 return dflt | 784 return dflt |
628 } | 785 } |
629 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) { | 786 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) { |
630 return dflt | 787 return dflt |
631 } | 788 } |
632 return cur | 789 return cur |
633 } | 790 } |
OLD | NEW |