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