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 "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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |