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

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

Issue 1355783002: Refactor keys and queries in datastore service and implementation. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: appease errcheck Created 5 years, 3 months 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
« no previous file with comments | « service/datastore/pls_test.go ('k') | service/datastore/properties_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 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
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « service/datastore/pls_test.go ('k') | service/datastore/properties_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698