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