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

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

Issue 1550903002: impl/memory: Fix time serialization encoding. (Closed) Base URL: https://github.com/luci/gae@master
Patch Set: Comments, tune-up. Created 4 years, 11 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_impl.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 "bytes"
8 "encoding/base64" 9 "encoding/base64"
9 "errors" 10 "errors"
10 "fmt" 11 "fmt"
11 "math" 12 "math"
12 "reflect" 13 "reflect"
13 "time" 14 "time"
14 15
15 "github.com/luci/gae/service/blobstore" 16 "github.com/luci/gae/service/blobstore"
16 ) 17 )
17 18
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after
167 "PTUnknown (and therefore PropertyType) exceeds 0x7e. Th is conflicts " + 168 "PTUnknown (and therefore PropertyType) exceeds 0x7e. Th is conflicts " +
168 "with serialize.WriteProperty's use of the high bit to indicate " + 169 "with serialize.WriteProperty's use of the high bit to indicate " +
169 "NoIndex and/or \"impl/memory\".increment's abil ity to guarantee " + 170 "NoIndex and/or \"impl/memory\".increment's abil ity to guarantee " +
170 "incrementability.") 171 "incrementability.")
171 } 172 }
172 } 173 }
173 174
174 // Property is a value plus an indicator of whether the value should be 175 // Property is a value plus an indicator of whether the value should be
175 // indexed. Name and Multiple are stored in the PropertyMap object. 176 // indexed. Name and Multiple are stored in the PropertyMap object.
176 type Property struct { 177 type Property struct {
177 » value interface{} 178 » // value is the Property's value. It is stored in an internal, opaque ty pe and
179 » // should not be directly exported to consumers. Rather, it can be acces sed
180 » // in its original value via Value().
181 » //
182 » // Specifically:
183 » //» - []byte- and string-based values are stored in a bytesByteSeque nce and
184 » //» stringByteSequence respectively.
185 » value interface{}
186
178 indexSetting IndexSetting 187 indexSetting IndexSetting
179 propType PropertyType 188 propType PropertyType
180 } 189 }
181 190
182 // MkProperty makes a new indexed* Property and returns it. If val is an 191 // MkProperty makes a new indexed* Property and returns it. If val is an
183 // invalid value, this panics (so don't do it). If you want to handle the error 192 // invalid value, this panics (so don't do it). If you want to handle the error
184 // normally, use SetValue(..., ShouldIndex) instead. 193 // normally, use SetValue(..., ShouldIndex) instead.
185 // 194 //
186 // *indexed if val is not an unindexable type like []byte. 195 // *indexed if val is not an unindexable type like []byte.
187 func MkProperty(val interface{}) Property { 196 func MkProperty(val interface{}) Property {
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 err := error(nil) 247 err := error(nil)
239 if checkValid && !x.Valid() { 248 if checkValid && !x.Valid() {
240 err = errors.New("invalid GeoPoint value") 249 err = errors.New("invalid GeoPoint value")
241 } 250 }
242 return PTGeoPoint, err 251 return PTGeoPoint, err
243 default: 252 default:
244 return PTUnknown, fmt.Errorf("gae: Property has bad type %T", v) 253 return PTUnknown, fmt.Errorf("gae: Property has bad type %T", v)
245 } 254 }
246 } 255 }
247 256
257 // RoundTime rounds a time.Time to microseconds, which is the (undocumented)
258 // way that the AppEngine SDK stores it.
259 func RoundTime(t time.Time) time.Time {
260 return t.Round(time.Microsecond)
261 }
262
263 // TimeToInt converts a time value to a datastore-appropraite integer value.
264 //
265 // This method truncates the time to microseconds and drops the timezone,
266 // because that's the (undocumented) way that the appengine SDK does it.
267 func TimeToInt(t time.Time) int64 {
268 if t.IsZero() {
269 return 0
270 }
271
272 t = RoundTime(t)
273 return t.Unix()*1e6 + int64(t.Nanosecond()/1e3)
274 }
275
276 // IntToTime converts a datastore time integer into its time.Time value.
277 func IntToTime(v int64) time.Time {
278 if v == 0 {
279 return time.Time{}
280 }
281 return RoundTime(time.Unix(int64(v/1e6), int64((v%1e6)*1e3))).UTC()
282 }
283
248 // timeLocationIsUTC tests if two time.Location are equal. 284 // timeLocationIsUTC tests if two time.Location are equal.
249 // 285 //
250 // This is tricky using the standard time API, as time is implicitly normalized 286 // This is tricky using the standard time API, as time is implicitly normalized
251 // to UTC and all equality checks are performed relative to that normalized 287 // to UTC and all equality checks are performed relative to that normalized
252 // time. To compensate, we instantiate two new time.Time using the respective 288 // time. To compensate, we instantiate two new time.Time using the respective
253 // Locations. 289 // Locations.
254 func timeLocationIsUTC(l *time.Location) bool { 290 func timeLocationIsUTC(l *time.Location) bool {
255 return time.Date(1970, 1, 1, 0, 0, 0, 0, l).Equal(utcTestTime) 291 return time.Date(1970, 1, 1, 0, 0, 0, 0, l).Equal(utcTestTime)
256 } 292 }
257 293
(...skipping 17 matching lines...) Expand all
275 o = v.String() 311 o = v.String()
276 } 312 }
277 case reflect.Float32, reflect.Float64: 313 case reflect.Float32, reflect.Float64:
278 o = v.Float() 314 o = v.Float()
279 case reflect.Slice: 315 case reflect.Slice:
280 if t.Elem().Kind() == reflect.Uint8 { 316 if t.Elem().Kind() == reflect.Uint8 {
281 o = v.Bytes() 317 o = v.Bytes()
282 } 318 }
283 case reflect.Struct: 319 case reflect.Struct:
284 if t == typeOfTime { 320 if t == typeOfTime {
285 // time in a Property can only hold microseconds
286 tim := v.Interface().(time.Time) 321 tim := v.Interface().(time.Time)
287 if !tim.IsZero() { 322 if !tim.IsZero() {
288 » » » » o = v.Interface().(time.Time).Round(time.Microse cond) 323 » » » » o = RoundTime(v.Interface().(time.Time))
289 } 324 }
290 } 325 }
291 } 326 }
292 return o 327 return o
293 } 328 }
294 329
295 // Value returns the current value held by this property. It's guaranteed to 330 // Value returns the current value held by this property. It's guaranteed to
296 // be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return 331 // be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return
297 // an error). 332 // an error).
298 func (p *Property) Value() interface{} { return p.value } 333 func (p *Property) Value() interface{} {
334 » switch p.propType {
335 » case PTBytes:
336 » » return p.value.(byteSequence).bytes()
337 » case PTString:
338 » » return p.value.(byteSequence).string()
339 » case PTBlobKey:
340 » » return blobstore.Key(p.value.(byteSequence).string())
341 » default:
342 » » return p.value
343 » }
344 }
299 345
300 // IndexSetting says whether or not the datastore should create indicies for 346 // IndexSetting says whether or not the datastore should create indicies for
301 // this value. 347 // this value.
302 func (p *Property) IndexSetting() IndexSetting { return p.indexSetting } 348 func (p *Property) IndexSetting() IndexSetting { return p.indexSetting }
303 349
304 // Type is the PT* type of the data contained in Value(). 350 // Type is the PT* type of the data contained in Value().
305 func (p *Property) Type() PropertyType { return p.propType } 351 func (p *Property) Type() PropertyType { return p.propType }
306 352
307 // SetValue sets the Value field of a Property, and ensures that its value 353 // SetValue sets the Value field of a Property, and ensures that its value
308 // conforms to the permissible types. That way, you're guaranteed that if you 354 // conforms to the permissible types. That way, you're guaranteed that if you
(...skipping 26 matching lines...) Expand all
335 // a nil-valued property into a struct will set that field to the zero 381 // a nil-valued property into a struct will set that field to the zero
336 // value. 382 // value.
337 func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) { 383 func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) {
338 pt := PTNull 384 pt := PTNull
339 if value != nil { 385 if value != nil {
340 value = UpconvertUnderlyingType(value) 386 value = UpconvertUnderlyingType(value)
341 if pt, err = PropertyTypeOf(value, true); err != nil { 387 if pt, err = PropertyTypeOf(value, true); err != nil {
342 return 388 return
343 } 389 }
344 } 390 }
391
392 // Convert value to internal Property storage type.
393 switch t := value.(type) {
394 case string:
395 value = stringByteSequence(t)
396 case blobstore.Key:
397 value = stringByteSequence(t)
398 case []byte:
399 value = bytesByteSequence(t)
400 case time.Time:
401 value = RoundTime(t)
402 }
403
345 p.propType = pt 404 p.propType = pt
346 p.value = value 405 p.value = value
347 p.indexSetting = is 406 p.indexSetting = is
348 return 407 return
349 } 408 }
350 409
351 // ForIndex gets a new Property with its value and type converted as if it were 410 // IndexTypeAndValue returns the type and value of the Property as it would
352 // being stored in a datastore index. See the doc on PropertyType for more info. 411 // show up in a datastore index.
353 func (p Property) ForIndex() Property { 412 //
354 » switch p.propType { 413 // This is used to operate on the Property as it would be stored in a datastore
355 » case PTNull, PTInt, PTBool, PTString, PTFloat, PTGeoPoint, PTKey: 414 // index, specifically for serialization and comparison.
356 » » return p 415 //
416 // The returned type will be the PropertyType used in the index. The returned
417 // value will be one of:
418 //» - bool
419 //» - int64
420 //» - float64
421 //» - string
422 //» - []byte
423 //» - GeoPoint
424 //» - *Key
425 func (p Property) IndexTypeAndValue() (PropertyType, interface{}) {
426 » switch t := p.propType; t {
427 » case PTNull, PTInt, PTBool, PTFloat, PTGeoPoint, PTKey:
428 » » return t, p.Value()
357 429
358 case PTTime: 430 case PTTime:
359 » » v, _ := p.Project(PTInt) 431 » » return PTInt, TimeToInt(p.value.(time.Time))
360 » » return Property{v, p.indexSetting, PTInt}
361 432
362 » case PTBytes, PTBlobKey: 433 » case PTString, PTBytes, PTBlobKey:
363 » » v, _ := p.Project(PTString) 434 » » return PTString, p.value.(byteSequence).value()
364 » » return Property{v, p.indexSetting, PTString} 435
436 » default:
437 » » panic(fmt.Errorf("unknown PropertyType: %s", t))
365 } 438 }
366 panic(fmt.Errorf("unknown PropertyType: %s", p.propType))
367 } 439 }
368 440
369 // Project can be used to project a Property retrieved from a Projection query 441 // Project can be used to project a Property retrieved from a Projection query
370 // into a different datatype. For example, if you have a PTInt property, you 442 // into a different datatype. For example, if you have a PTInt property, you
371 // could Project(PTTime) to convert it to a time.Time. The following conversions 443 // could Project(PTTime) to convert it to a time.Time. The following conversions
372 // are supported: 444 // are supported:
445 // PTString <-> PTBlobKey
446 // PTString <-> PTBytes
373 // PTXXX <-> PTXXX (i.e. identity) 447 // PTXXX <-> PTXXX (i.e. identity)
374 // PTInt <-> PTTime 448 // PTInt <-> PTTime
375 // PTString <-> PTBlobKey
376 // PTString <-> PTBytes
377 // PTNull <-> Anything 449 // PTNull <-> Anything
378 func (p *Property) Project(to PropertyType) (interface{}, error) { 450 func (p *Property) Project(to PropertyType) (interface{}, error) {
379 » switch { 451 » if to == PTNull {
380 » case to == p.propType: 452 » » return nil, nil
381 » » return p.value, nil 453 » }
382 454
383 » case to == PTInt && p.propType == PTTime: 455 » pt, v := p.propType, p.value
384 » » t := p.value.(time.Time) 456 » switch pt {
385 » » v := uint64(t.Unix())*1e6 + uint64(t.Nanosecond()/1e3) 457 » case PTBytes, PTString, PTBlobKey:
386 » » return int64(v), nil 458 » » v := v.(byteSequence)
459 » » switch to {
460 » » case PTBytes:
461 » » » return v.bytes(), nil
462 » » case PTString:
463 » » » return v.string(), nil
464 » » case PTBlobKey:
465 » » » return blobstore.Key(v.string()), nil
466 » » }
387 467
388 » case to == PTTime && p.propType == PTInt: 468 » case PTTime:
389 » » v := p.value.(int64) 469 » » switch to {
390 » » t := time.Unix(int64(v/1e6), int64((v%1e6)*1e3)) 470 » » case PTInt:
391 » » if t.IsZero() { 471 » » » return TimeToInt(v.(time.Time)), nil
392 » » » return time.Time{}, nil 472 » » case PTTime:
473 » » » return v, nil
393 } 474 }
394 return t.UTC(), nil
395 475
396 » case to == PTString && p.propType == PTBytes: 476 » case PTInt:
397 » » return string(p.value.([]byte)), nil 477 » » switch to {
478 » » case PTInt:
479 » » » return v, nil
480 » » case PTTime:
481 » » » return IntToTime(v.(int64)), nil
482 » » }
398 483
399 » case to == PTString && p.propType == PTBlobKey: 484 » case to:
400 » » return string(p.value.(blobstore.Key)), nil 485 » » return v, nil
401 486
402 » case to == PTBytes && p.propType == PTString: 487 » case PTNull:
403 » » return []byte(p.value.(string)), nil
404
405 » case to == PTBlobKey && p.propType == PTString:
406 » » return blobstore.Key(p.value.(string)), nil
407
408 » case to == PTNull:
409 » » return nil, nil
410
411 » case p.propType == PTNull:
412 switch to { 488 switch to {
413 case PTInt: 489 case PTInt:
414 return int64(0), nil 490 return int64(0), nil
415 case PTTime: 491 case PTTime:
416 return time.Time{}, nil 492 return time.Time{}, nil
417 case PTBool: 493 case PTBool:
418 return false, nil 494 return false, nil
419 case PTBytes: 495 case PTBytes:
420 return []byte(nil), nil 496 return []byte(nil), nil
421 case PTString: 497 case PTString:
422 return "", nil 498 return "", nil
423 case PTFloat: 499 case PTFloat:
424 return float64(0), nil 500 return float64(0), nil
425 case PTGeoPoint: 501 case PTGeoPoint:
426 return GeoPoint{}, nil 502 return GeoPoint{}, nil
427 case PTKey: 503 case PTKey:
428 return nil, nil 504 return nil, nil
429 case PTBlobKey: 505 case PTBlobKey:
430 return blobstore.Key(""), nil 506 return blobstore.Key(""), nil
431 } 507 }
432 fallthrough
433 default:
434 return nil, fmt.Errorf("unable to project %s to %s", p.propType, to)
435 } 508 }
509 return nil, fmt.Errorf("unable to project %s to %s", pt, to)
436 } 510 }
437 511
438 func cmpVals(a, b interface{}, t PropertyType) int { 512 func cmpFloat(a, b float64) int {
439 » cmpFloat := func(a, b float64) int { 513 » if a == b {
440 » » if a == b { 514 » » return 0
441 » » » return 0 515 » }
442 » » } 516 » if a > b {
443 » » if a > b { 517 » » return 1
444 » » » return 1 518 » }
445 » » } 519 » return -1
520 }
521
522 // Less returns true iff p would sort before other.
523 //
524 // This uses datastore's index rules for sorting (see GetIndexTypeAndValue).
525 func (p *Property) Less(other *Property) bool {
526 » return p.Compare(other) < 0
527 }
528
529 // Equal returns true iff p and other have identical index representations.
530 //
531 // This uses datastore's index rules for sorting (see GetIndexTypeAndValue).
532 func (p *Property) Equal(other *Property) bool {
533 » return p.Compare(other) == 0
534 }
535
536 // Compare compares this Property to another, returning a trinary value
537 // indicating where it would sort relative to the other in datastore.
538 //
539 // It returns:
540 //» <0 if the Property would sort before `other`.
541 //» >0 if the Property would after before `other`.
542 //» 0 if the Property equals `other`.
543 //
544 // This uses datastore's index rules for sorting (see GetIndexTypeAndValue).
545 func (p *Property) Compare(other *Property) int {
546 » if p.indexSetting && !other.indexSetting {
547 » » return 1
548 » } else if !p.indexSetting && other.indexSetting {
446 return -1 549 return -1
447 } 550 }
448 551
449 » switch t { 552 » at, av := p.IndexTypeAndValue()
553 » bt, bv := other.IndexTypeAndValue()
554 » if cmp := int(at) - int(bt); cmp != 0 {
555 » » return cmp
556 » }
557
558 » switch t := at; t {
450 case PTNull: 559 case PTNull:
451 return 0 560 return 0
452 561
453 case PTBool: 562 case PTBool:
454 » » a, b := a.(bool), b.(bool) 563 » » a, b := av.(bool), bv.(bool)
455 if a == b { 564 if a == b {
456 return 0 565 return 0
457 } 566 }
458 if a && !b { 567 if a && !b {
459 return 1 568 return 1
460 } 569 }
461 return -1 570 return -1
462 571
463 case PTInt: 572 case PTInt:
464 » » a, b := a.(int64), b.(int64) 573 » » a, b := av.(int64), bv.(int64)
465 if a == b { 574 if a == b {
466 return 0 575 return 0
467 } 576 }
468 if a > b { 577 if a > b {
469 return 1 578 return 1
470 } 579 }
471 return -1 580 return -1
472 581
473 case PTString: 582 case PTString:
474 » » a, b := a.(string), b.(string) 583 » » return cmpByteSequence(p.value.(byteSequence), other.value.(byte Sequence))
475 » » if a == b {
476 » » » return 0
477 » » }
478 » » if a > b {
479 » » » return 1
480 » » }
481 » » return -1
482 584
483 case PTFloat: 585 case PTFloat:
484 » » return cmpFloat(a.(float64), b.(float64)) 586 » » return cmpFloat(av.(float64), bv.(float64))
485 587
486 case PTGeoPoint: 588 case PTGeoPoint:
487 » » a, b := a.(GeoPoint), b.(GeoPoint) 589 » » a, b := av.(GeoPoint), bv.(GeoPoint)
488 cmp := cmpFloat(a.Lat, b.Lat) 590 cmp := cmpFloat(a.Lat, b.Lat)
489 if cmp != 0 { 591 if cmp != 0 {
490 return cmp 592 return cmp
491 } 593 }
492 return cmpFloat(a.Lng, b.Lng) 594 return cmpFloat(a.Lng, b.Lng)
493 595
494 case PTKey: 596 case PTKey:
495 » » a, b := a.(*Key), b.(*Key) 597 » » a, b := av.(*Key), bv.(*Key)
496 if a.Equal(b) { 598 if a.Equal(b) {
497 return 0 599 return 0
498 } 600 }
499 if b.Less(a) { 601 if b.Less(a) {
500 return 1 602 return 1
501 } 603 }
502 return -1 604 return -1
503 605
504 default: 606 default:
505 panic(fmt.Errorf("uncomparable type: %s", t)) 607 panic(fmt.Errorf("uncomparable type: %s", t))
506 } 608 }
507 } 609 }
508 610
509 // Less returns true iff p would sort before other.
510 //
511 // This uses datastore's index rules for sorting (e.g.
512 // []byte("hello") == "hello")
513 func (p *Property) Less(other *Property) bool {
514 if p.indexSetting && !other.indexSetting {
515 return true
516 } else if !p.indexSetting && other.indexSetting {
517 return false
518 }
519 a, b := p.ForIndex(), other.ForIndex()
520 cmp := int(a.propType) - int(b.propType)
521 if cmp < 0 {
522 return true
523 } else if cmp > 0 {
524 return false
525 }
526 return cmpVals(a.value, b.value, a.propType) < 0
527 }
528
529 // Equal returns true iff p and other have identical index representations.
530 //
531 // This uses datastore's index rules for sorting (e.g.
532 // []byte("hello") == "hello")
533 func (p *Property) Equal(other *Property) bool {
534 ret := p.indexSetting == other.indexSetting
535 if ret {
536 a, b := p.ForIndex(), other.ForIndex()
537 ret = a.propType == b.propType && cmpVals(a.value, b.value, a.pr opType) == 0
538 }
539 return ret
540 }
541
542 // GQL returns a correctly formatted Cloud Datastore GQL literal which 611 // GQL returns a correctly formatted Cloud Datastore GQL literal which
543 // is valid for a comparison value in the `WHERE` clause. 612 // is valid for a comparison value in the `WHERE` clause.
544 // 613 //
545 // The flavor of GQL that this emits is defined here: 614 // The flavor of GQL that this emits is defined here:
546 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference 615 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference
547 // 616 //
548 // NOTE: GeoPoint values are emitted with speculated future syntax. There is 617 // NOTE: GeoPoint values are emitted with speculated future syntax. There is
549 // currently no syntax for literal GeoPoint values. 618 // currently no syntax for literal GeoPoint values.
550 func (p *Property) GQL() string { 619 func (p *Property) GQL() string {
620 v := p.Value()
551 switch p.propType { 621 switch p.propType {
552 case PTNull: 622 case PTNull:
553 return "NULL" 623 return "NULL"
554 624
555 case PTInt, PTFloat, PTBool: 625 case PTInt, PTFloat, PTBool:
556 » » return fmt.Sprint(p.value) 626 » » return fmt.Sprint(v)
557 627
558 case PTString: 628 case PTString:
559 » » return gqlQuoteString(p.value.(string)) 629 » » return gqlQuoteString(v.(string))
560 630
561 case PTBytes: 631 case PTBytes:
562 return fmt.Sprintf("BLOB(%q)", 632 return fmt.Sprintf("BLOB(%q)",
563 » » » base64.URLEncoding.EncodeToString(p.value.([]byte))) 633 » » » base64.URLEncoding.EncodeToString(v.([]byte)))
564 634
565 case PTBlobKey: 635 case PTBlobKey:
566 return fmt.Sprintf("BLOBKEY(%s)", gqlQuoteString( 636 return fmt.Sprintf("BLOBKEY(%s)", gqlQuoteString(
567 » » » string(p.value.(blobstore.Key)))) 637 » » » string(v.(blobstore.Key))))
568 638
569 case PTKey: 639 case PTKey:
570 » » return p.value.(*Key).GQL() 640 » » return v.(*Key).GQL()
571 641
572 case PTTime: 642 case PTTime:
573 » » return fmt.Sprintf("DATETIME(%s)", p.value.(time.Time).Format(ti me.RFC3339Nano)) 643 » » return fmt.Sprintf("DATETIME(%s)", v.(time.Time).Format(time.RFC 3339Nano))
574 644
575 case PTGeoPoint: 645 case PTGeoPoint:
576 // note that cloud SQL doesn't support this yet, but take a good guess at 646 // note that cloud SQL doesn't support this yet, but take a good guess at
577 // it. 647 // it.
578 » » v := p.value.(GeoPoint) 648 » » v := v.(GeoPoint)
579 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng) 649 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng)
580 } 650 }
581 panic(fmt.Errorf("bad type: %s", p.propType)) 651 panic(fmt.Errorf("bad type: %s", p.propType))
582 } 652 }
583 653
584 // PropertySlice is a slice of Properties. It implements sort.Interface. 654 // PropertySlice is a slice of Properties. It implements sort.Interface.
585 type PropertySlice []Property 655 type PropertySlice []Property
586 656
587 func (s PropertySlice) Len() int { return len(s) } 657 func (s PropertySlice) Len() int { return len(s) }
588 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 658 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
589 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) } 659 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) }
590 660
591 // EstimateSize estimates the amount of space that this Property would consume 661 // EstimateSize estimates the amount of space that this Property would consume
592 // if it were committed as part of an entity in the real production datastore. 662 // if it were committed as part of an entity in the real production datastore.
593 // 663 //
594 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1 664 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
595 // as a guide for these values. 665 // as a guide for these values.
596 func (p *Property) EstimateSize() int64 { 666 func (p *Property) EstimateSize() int64 {
597 switch p.Type() { 667 switch p.Type() {
598 case PTNull: 668 case PTNull:
599 return 1 669 return 1
600 case PTBool: 670 case PTBool:
601 return 1 + 4 671 return 1 + 4
602 case PTInt, PTTime, PTFloat: 672 case PTInt, PTTime, PTFloat:
603 return 1 + 8 673 return 1 + 8
604 case PTGeoPoint: 674 case PTGeoPoint:
605 return 1 + (8 * 2) 675 return 1 + (8 * 2)
606 case PTString: 676 case PTString:
607 » » return 1 + int64(len(p.value.(string))) 677 » » return 1 + int64(len(p.Value().(string)))
608 case PTBlobKey: 678 case PTBlobKey:
609 » » return 1 + int64(len(p.value.(blobstore.Key))) 679 » » return 1 + int64(len(p.Value().(blobstore.Key)))
610 case PTBytes: 680 case PTBytes:
611 » » return 1 + int64(len(p.value.([]byte))) 681 » » return 1 + int64(len(p.Value().([]byte)))
612 case PTKey: 682 case PTKey:
613 » » return 1 + p.value.(*Key).EstimateSize() 683 » » return 1 + p.Value().(*Key).EstimateSize()
614 } 684 }
615 panic(fmt.Errorf("Unknown property type: %s", p.Type().String())) 685 panic(fmt.Errorf("Unknown property type: %s", p.Type().String()))
616 } 686 }
617 687
618 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to 688 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to
619 // abstract the meta argument for RawInterface.GetMulti. 689 // abstract the meta argument for RawInterface.GetMulti.
620 type MetaGetter interface { 690 type MetaGetter interface {
621 // GetMeta will get information about the field which has the struct tag in 691 // GetMeta will get information about the field which has the struct tag in
622 // the form of `gae:"$<key>[,<default>]?"`. 692 // the form of `gae:"$<key>[,<default>]?"`.
623 // 693 //
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after
821 // Example: 891 // Example:
822 // pls.GetMetaDefault("foo", 100).(int64) 892 // pls.GetMetaDefault("foo", 100).(int64)
823 func GetMetaDefault(getter MetaGetter, key string, dflt interface{}) interface{} { 893 func GetMetaDefault(getter MetaGetter, key string, dflt interface{}) interface{} {
824 dflt = UpconvertUnderlyingType(dflt) 894 dflt = UpconvertUnderlyingType(dflt)
825 cur, ok := getter.GetMeta(key) 895 cur, ok := getter.GetMeta(key)
826 if !ok || (dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt)) { 896 if !ok || (dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt)) {
827 return dflt 897 return dflt
828 } 898 }
829 return cur 899 return cur
830 } 900 }
901
902 // byteSequence is a generic interface for an object that can be represented as
903 // a sequence of bytes. Its implementations are used internally by Property to
904 // enable zero-copy conversion and comparisons between byte sequence types.
905 type byteSequence interface {
906 // len returns the number of bytes in the sequence.
907 len() int
908 // get returns the byte at the specified index.
909 get(int) byte
910 // value returns the sequence's primitive type.
911 value() interface{}
912 // string returns the sequence as a string (may cause a copy if not nati ve).
913 string() string
914 // bytes returns the sequence as a []byte (may cause a copy if not nativ e).
915 bytes() []byte
916 // fastCmp is an implementation-specific comparison method. If it return s
917 // true in its second return value, the comparison was performed and its
918 // result is in the first return value. Otherwise, the fast comparison c ould
919 // not be performed.
920 fastCmp(o byteSequence) (int, bool)
921 }
922
923 func cmpByteSequence(a, b byteSequence) int {
924 if v, ok := a.fastCmp(b); ok {
925 return v
926 }
927
928 // Byte-by-byte "slow" comparison.
929 ld := a.len() - b.len()
930 if ld < 0 {
931 ld = -ld
932 }
933 for i := 0; i < ld; i++ {
934 av, bv := a.get(i), b.get(i)
935 switch {
936 case av < bv:
937 return -1
938 case av > bv:
939 return 1
940 }
941 }
942
943 return ld
944 }
945
946 // bytesByteSequence is a byteSequence implementation for a byte slice.
947 type bytesByteSequence []byte
948
949 func (s bytesByteSequence) len() int { return len(s) }
950 func (s bytesByteSequence) get(i int) byte { return s[i] }
951 func (s bytesByteSequence) value() interface{} { return []byte(s) }
952 func (s bytesByteSequence) string() string { return string(s) }
953 func (s bytesByteSequence) bytes() []byte { return []byte(s) }
954 func (s bytesByteSequence) fastCmp(o byteSequence) (int, bool) {
955 if t, ok := o.(bytesByteSequence); ok {
956 return bytes.Compare([]byte(s), []byte(t)), true
957 }
958 return 0, false
959 }
960
961 // stringByteSequence is a byteSequence implementation for a string.
962 type stringByteSequence string
963
964 func (s stringByteSequence) len() int { return len(s) }
965 func (s stringByteSequence) get(i int) byte { return s[i] }
966 func (s stringByteSequence) value() interface{} { return string(s) }
967 func (s stringByteSequence) string() string { return string(s) }
968 func (s stringByteSequence) bytes() []byte { return []byte(s) }
969 func (s stringByteSequence) fastCmp(o byteSequence) (int, bool) {
970 if t, ok := o.(stringByteSequence); ok {
971 // This pattern is used for string comparison in strings.Compare .
972 if string(s) == string(t) {
973 return 0, true
974 }
975 if string(s) < string(t) {
976 return -1, true
977 }
978 return 0, true
979 }
980 return 0, false
981 }
OLDNEW
« no previous file with comments | « service/datastore/pls_impl.go ('k') | service/datastore/properties_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698