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

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: fix comments 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
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 "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
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
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
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 {
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
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
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 {
456 cmpFloat := func(a, b float64) int {
457 if a == b {
458 return 0
459 }
460 if a > b {
461 return 1
462 }
463 return -1
464 }
465
466 switch t {
467 case PTNull:
468 return 0
469
470 case PTBool:
471 a, b := a.(bool), b.(bool)
472 if a == b {
473 return 0
474 }
475 if a && !b {
476 return 1
477 }
478 return -1
479
480 case PTInt:
481 a, b := a.(int64), b.(int64)
482 if a == b {
483 return 0
484 }
485 if a > b {
486 return 1
487 }
488 return -1
489
490 case PTString:
491 a, b := a.(string), b.(string)
492 if a == b {
493 return 0
494 }
495 if a > b {
496 return 1
497 }
498 return -1
499
500 case PTFloat:
501 return cmpFloat(a.(float64), b.(float64))
502
503 case PTGeoPoint:
504 a, b := a.(GeoPoint), b.(GeoPoint)
505 cmp := cmpFloat(a.Lat, b.Lat)
506 if cmp != 0 {
507 return cmp
508 }
509 return cmpFloat(a.Lng, b.Lng)
510
511 case PTKey:
512 a, b := a.(*Key), b.(*Key)
513 if a.Equal(b) {
514 return 0
515 }
516 if b.Less(a) {
517 return 1
518 }
519 return -1
520
521 default:
522 panic(fmt.Errorf("uncomparable type: %s", t))
523 }
524 }
525
526 func (p *Property) Less(other *Property) bool {
527 if p.indexSetting && !other.indexSetting {
528 return true
529 } else if !p.indexSetting && other.indexSetting {
530 return false
531 }
532 a, b := p.ForIndex(), other.ForIndex()
533 cmp := int(a.propType) - int(b.propType)
534 if cmp < 0 {
535 return true
536 } else if cmp > 0 {
537 return false
538 }
539 return cmpVals(a.value, b.value, a.propType) < 0
540 }
541
542 func (p *Property) Equal(other *Property) bool {
543 ret := p.indexSetting == other.indexSetting
544 if ret {
545 a, b := p.ForIndex(), other.ForIndex()
546 ret = a.propType == b.propType && cmpVals(a.value, b.value, a.pr opType) == 0
547 }
548 return ret
549 }
550
551 func (p *Property) GQL() string {
552 switch p.propType {
553 case PTNull:
554 return "NULL"
555
556 case PTInt, PTFloat, PTBool:
557 return fmt.Sprint(p.value)
558
559 case PTString:
560 return gqlQuoteString(p.value.(string))
561
562 case PTBytes:
563 return fmt.Sprintf("BLOB(%q)",
564 base64.URLEncoding.EncodeToString(p.value.([]byte)))
565
566 case PTBlobKey:
567 return fmt.Sprintf("BLOBKEY(%s)", gqlQuoteString(
568 string(p.value.(blobstore.Key))))
569
570 case PTKey:
571 aid, ns, toks := p.value.(*Key).Split()
572 ret := &bytes.Buffer{}
573 fmt.Fprintf(ret, "KEY(DATASET(%s)", gqlQuoteString(aid))
574 if ns != "" {
575 fmt.Fprintf(ret, ", NAMESPACE(%s)", gqlQuoteString(ns))
576 }
577 for _, t := range toks {
578 if t.IntID != 0 {
579 fmt.Fprintf(ret, ", %s, %d", gqlQuoteString(t.Ki nd), t.IntID)
580 } else {
581 fmt.Fprintf(ret, ", %s, %s", gqlQuoteString(t.Ki nd), gqlQuoteString(t.StringID))
582 }
583 }
584 ret.WriteString(")")
585 return ret.String()
586
587 case PTTime:
588 return fmt.Sprintf("DATETIME(%s)", p.value.(time.Time).Format(ti me.RFC3339Nano))
589
590 case PTGeoPoint:
591 // note that cloud SQL doesn't support this yet, but take a good guess at
592 // it.
593 v := p.value.(GeoPoint)
594 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng)
595 }
596 panic(fmt.Errorf("bad type: %s", p.propType))
597 }
598
599 type PropertySlice []Property
600
601 func (s PropertySlice) Len() int { return len(s) }
602 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
603 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) }
604
453 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to 605 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to
454 // abstract the meta argument for RawInterface.GetMulti. 606 // abstract the meta argument for RawInterface.GetMulti.
455 type MetaGetter interface { 607 type MetaGetter interface {
456 // GetMeta will get information about the field which has the struct tag in 608 // GetMeta will get information about the field which has the struct tag in
457 // the form of `gae:"$<key>[,<default>]?"`. 609 // the form of `gae:"$<key>[,<default>]?"`.
458 // 610 //
459 // Supported metadata types are: 611 // Supported metadata types are:
460 // int64 - may have default (ascii encoded base-10) 612 // int64 - may have default (ascii encoded base-10)
461 // string - may have default 613 // string - may have default
462 // Toggle - MUST have default ("true" or "false") 614 // Toggle - MUST have default ("true" or "false")
463 » // Key - NO default allowed 615 » // *Key - NO default allowed
464 // 616 //
465 // Struct fields of type Toggle (which is an Auto/On/Off) require you to 617 // 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 618 // 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 619 // tag, and GetMeta will return the combined value as a regular boolean true
468 // or false value. 620 // or false value.
469 // Example: 621 // Example:
470 // type MyStruct struct { 622 // type MyStruct struct {
471 // CoolField int64 `gae:"$id,1"` 623 // CoolField int64 `gae:"$id,1"`
472 // } 624 // }
473 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") 625 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id")
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
624 dflt = UpconvertUnderlyingType(dflt) 776 dflt = UpconvertUnderlyingType(dflt)
625 cur, err := gm(key) 777 cur, err := gm(key)
626 if err != nil { 778 if err != nil {
627 return dflt 779 return dflt
628 } 780 }
629 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) { 781 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) {
630 return dflt 782 return dflt
631 } 783 }
632 return cur 784 return cur
633 } 785 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698