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

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: 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 {
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
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 {
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698