1// Copyright 2014 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package datastore 16 17import ( 18 "context" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "reflect" 23 "sort" 24 "strings" 25 "testing" 26 "time" 27 28 "cloud.google.com/go/internal/testutil" 29 "github.com/golang/protobuf/proto" 30 "github.com/google/go-cmp/cmp" 31 pb "google.golang.org/genproto/googleapis/datastore/v1" 32 "google.golang.org/grpc" 33) 34 35type ( 36 myBlob []byte 37 myByte byte 38 myString string 39) 40 41func makeMyByteSlice(n int) []myByte { 42 b := make([]myByte, n) 43 for i := range b { 44 b[i] = myByte(i) 45 } 46 return b 47} 48 49func makeInt8Slice(n int) []int8 { 50 b := make([]int8, n) 51 for i := range b { 52 b[i] = int8(i) 53 } 54 return b 55} 56 57func makeUint8Slice(n int) []uint8 { 58 b := make([]uint8, n) 59 for i := range b { 60 b[i] = uint8(i) 61 } 62 return b 63} 64 65func newKey(stringID string, parent *Key) *Key { 66 return NameKey("kind", stringID, parent) 67} 68 69var ( 70 testKey0 = newKey("name0", nil) 71 testKey1a = newKey("name1", nil) 72 testKey1b = newKey("name1", nil) 73 testKey2a = newKey("name2", testKey0) 74 testKey2b = newKey("name2", testKey0) 75 testGeoPt0 = GeoPoint{Lat: 1.2, Lng: 3.4} 76 testGeoPt1 = GeoPoint{Lat: 5, Lng: 10} 77 testBadGeoPt = GeoPoint{Lat: 1000, Lng: 34} 78 79 ts = time.Unix(1e9, 0).UTC() 80) 81 82type B0 struct { 83 B []byte `datastore:",noindex"` 84} 85 86type B1 struct { 87 B []int8 88} 89 90type B2 struct { 91 B myBlob `datastore:",noindex"` 92} 93 94type B3 struct { 95 B []myByte `datastore:",noindex"` 96} 97 98type B4 struct { 99 B [][]byte 100} 101 102type C0 struct { 103 I int 104 C chan int 105} 106 107type C1 struct { 108 I int 109 C *chan int 110} 111 112type C2 struct { 113 I int 114 C []chan int 115} 116 117type C3 struct { 118 C string 119} 120 121type c4 struct { 122 C string 123} 124 125type E struct{} 126 127type G0 struct { 128 G GeoPoint 129} 130 131type G1 struct { 132 G []GeoPoint 133} 134 135type K0 struct { 136 K *Key 137} 138 139type K1 struct { 140 K []*Key 141} 142 143type S struct { 144 St string 145} 146 147type NoOmit struct { 148 A string 149 B int `datastore:"Bb"` 150 C bool `datastore:",noindex"` 151} 152 153type OmitAll struct { 154 A string `datastore:",omitempty"` 155 B int `datastore:"Bb,omitempty"` 156 C bool `datastore:",omitempty,noindex"` 157 D time.Time `datastore:",omitempty"` 158 F []int `datastore:",omitempty"` 159} 160 161type Omit struct { 162 A string `datastore:",omitempty"` 163 B int `datastore:"Bb,omitempty"` 164 C bool `datastore:",omitempty,noindex"` 165 D time.Time `datastore:",omitempty"` 166 F []int `datastore:",omitempty"` 167 S `datastore:",omitempty"` 168} 169 170type NoOmits struct { 171 No []NoOmit `datastore:",omitempty"` 172 S `datastore:",omitempty"` 173 Ss S `datastore:",omitempty"` 174} 175 176type N0 struct { 177 X0 178 Nonymous X0 179 Ignore string `datastore:"-"` 180 Other string 181} 182 183type N1 struct { 184 X0 185 Nonymous []X0 186 Ignore string `datastore:"-"` 187 Other string 188} 189 190type N2 struct { 191 N1 `datastore:"red"` 192 Green N1 `datastore:"green"` 193 Blue N1 194 White N1 `datastore:"-"` 195} 196 197type N3 struct { 198 C3 `datastore:"red"` 199} 200 201type N4 struct { 202 c4 203} 204 205type N5 struct { 206 c4 `datastore:"red"` 207} 208 209type O0 struct { 210 I int64 211} 212 213type O1 struct { 214 I int32 215} 216 217type U0 struct { 218 U uint 219} 220 221type U1 struct { 222 U string 223} 224 225type T struct { 226 T time.Time 227} 228 229type X0 struct { 230 S string 231 I int 232 i int 233} 234 235type X1 struct { 236 S myString 237 I int32 238 J int64 239} 240 241type X2 struct { 242 Z string 243} 244 245type X3 struct { 246 S bool 247 I int 248} 249 250type Y0 struct { 251 B bool 252 F []float64 253 G []float64 254} 255 256type Y1 struct { 257 B bool 258 F float64 259} 260 261type Y2 struct { 262 B bool 263 F []int64 264} 265 266type Pointers struct { 267 Pi *int 268 Ps *string 269 Pb *bool 270 Pf *float64 271 Pg *GeoPoint 272 Pt *time.Time 273} 274 275type PointersOmitEmpty struct { 276 Pi *int `datastore:",omitempty"` 277 Ps *string `datastore:",omitempty"` 278 Pb *bool `datastore:",omitempty"` 279 Pf *float64 `datastore:",omitempty"` 280 Pg *GeoPoint `datastore:",omitempty"` 281 Pt *time.Time `datastore:",omitempty"` 282} 283 284func populatedPointers() *Pointers { 285 var ( 286 i int 287 s string 288 b bool 289 f float64 290 g GeoPoint 291 t time.Time 292 ) 293 return &Pointers{ 294 Pi: &i, 295 Ps: &s, 296 Pb: &b, 297 Pf: &f, 298 Pg: &g, 299 Pt: &t, 300 } 301} 302 303type Tagged struct { 304 A int `datastore:"a,noindex"` 305 B []int `datastore:"b"` 306 C int `datastore:",noindex"` 307 D int `datastore:""` 308 E int 309 I int `datastore:"-"` 310 J int `datastore:",noindex" json:"j"` 311 312 Y0 `datastore:"-"` 313 Z chan int `datastore:"-"` 314} 315 316type InvalidTagged1 struct { 317 I int `datastore:"\t"` 318} 319 320type InvalidTagged2 struct { 321 I int 322 J int `datastore:"I"` 323} 324 325type InvalidTagged3 struct { 326 X string `datastore:"-,noindex"` 327} 328 329type InvalidTagged4 struct { 330 X string `datastore:",garbage"` 331} 332 333type Inner1 struct { 334 W int32 335 X string 336} 337 338type Inner2 struct { 339 Y float64 340} 341 342type Inner3 struct { 343 Z bool 344} 345 346type Inner5 struct { 347 WW int 348} 349 350type Inner4 struct { 351 X Inner5 352} 353 354type Outer struct { 355 A int16 356 I []Inner1 357 J Inner2 358 Inner3 359} 360 361type OuterFlatten struct { 362 A int16 363 I []Inner1 `datastore:",flatten"` 364 J Inner2 `datastore:",flatten,noindex"` 365 Inner3 `datastore:",flatten"` 366 K Inner4 `datastore:",flatten"` 367 L *Inner2 `datastore:",flatten"` 368} 369 370type OuterEquivalent struct { 371 A int16 372 IDotW []int32 `datastore:"I.W"` 373 IDotX []string `datastore:"I.X"` 374 JDotY float64 `datastore:"J.Y"` 375 Z bool 376} 377 378type Dotted struct { 379 A DottedA `datastore:"A0.A1.A2"` 380} 381 382type DottedA struct { 383 B DottedB `datastore:"B3"` 384} 385 386type DottedB struct { 387 C int `datastore:"C4.C5"` 388} 389 390type SliceOfSlices struct { 391 I int 392 S []struct { 393 J int 394 F []float64 395 } `datastore:",flatten"` 396} 397 398type Recursive struct { 399 I int 400 R []Recursive 401} 402 403type MutuallyRecursive0 struct { 404 I int 405 R []MutuallyRecursive1 406} 407 408type MutuallyRecursive1 struct { 409 I int 410 R []MutuallyRecursive0 411} 412 413type EntityWithKey struct { 414 I int 415 S string 416 K *Key `datastore:"__key__"` 417} 418 419type WithNestedEntityWithKey struct { 420 N EntityWithKey 421} 422 423type WithNonKeyField struct { 424 I int 425 K string `datastore:"__key__"` 426} 427 428type NestedWithNonKeyField struct { 429 N WithNonKeyField 430} 431 432type Basic struct { 433 A string 434} 435 436type PtrToStructField struct { 437 B *Basic 438 C *Basic `datastore:"c,noindex"` 439 *Basic 440 D []*Basic 441} 442 443type EmbeddedTime struct { 444 time.Time 445} 446 447type SpecialTime struct { 448 MyTime EmbeddedTime 449} 450 451type Doubler struct { 452 S string 453 I int64 454 B bool 455} 456 457type Repeat struct { 458 Key string 459 Value []byte 460} 461 462type Repeated struct { 463 Repeats []Repeat 464} 465 466func (d *Doubler) Load(props []Property) error { 467 return LoadStruct(d, props) 468} 469 470func (d *Doubler) Save() ([]Property, error) { 471 // Save the default Property slice to an in-memory buffer (a PropertyList). 472 props, err := SaveStruct(d) 473 if err != nil { 474 return nil, err 475 } 476 var list PropertyList 477 if err := list.Load(props); err != nil { 478 return nil, err 479 } 480 481 // Edit that PropertyList, and send it on. 482 for i := range list { 483 switch v := list[i].Value.(type) { 484 case string: 485 // + means string concatenation. 486 list[i].Value = v + v 487 case int64: 488 // + means integer addition. 489 list[i].Value = v + v 490 } 491 } 492 return list.Save() 493} 494 495var _ PropertyLoadSaver = (*Doubler)(nil) 496 497type Deriver struct { 498 S, Derived, Ignored string 499} 500 501func (e *Deriver) Load(props []Property) error { 502 for _, p := range props { 503 if p.Name != "S" { 504 continue 505 } 506 e.S = p.Value.(string) 507 e.Derived = "derived+" + e.S 508 } 509 return nil 510} 511 512func (e *Deriver) Save() ([]Property, error) { 513 return []Property{ 514 { 515 Name: "S", 516 Value: e.S, 517 }, 518 }, nil 519} 520 521var _ PropertyLoadSaver = (*Deriver)(nil) 522 523type BadMultiPropEntity struct{} 524 525func (e *BadMultiPropEntity) Load(props []Property) error { 526 return errors.New("unimplemented") 527} 528 529func (e *BadMultiPropEntity) Save() ([]Property, error) { 530 // Write multiple properties with the same name "I". 531 var props []Property 532 for i := 0; i < 3; i++ { 533 props = append(props, Property{ 534 Name: "I", 535 Value: int64(i), 536 }) 537 } 538 return props, nil 539} 540 541var _ PropertyLoadSaver = (*BadMultiPropEntity)(nil) 542 543type testCase struct { 544 desc string 545 src interface{} 546 want interface{} 547 putErr string 548 getErr string 549} 550 551var testCases = []testCase{ 552 { 553 "chan save fails", 554 &C0{I: -1}, 555 &E{}, 556 "unsupported struct field", 557 "", 558 }, 559 { 560 "*chan save fails", 561 &C1{I: -1}, 562 &E{}, 563 "unsupported struct field", 564 "", 565 }, 566 { 567 "[]chan save fails", 568 &C2{I: -1, C: make([]chan int, 8)}, 569 &E{}, 570 "unsupported struct field", 571 "", 572 }, 573 { 574 "chan load fails", 575 &C3{C: "not a chan"}, 576 &C0{}, 577 "", 578 "type mismatch", 579 }, 580 { 581 "*chan load fails", 582 &C3{C: "not a *chan"}, 583 &C1{}, 584 "", 585 "type mismatch", 586 }, 587 { 588 "[]chan load fails", 589 &C3{C: "not a []chan"}, 590 &C2{}, 591 "", 592 "type mismatch", 593 }, 594 { 595 "empty struct", 596 &E{}, 597 &E{}, 598 "", 599 "", 600 }, 601 { 602 "geopoint", 603 &G0{G: testGeoPt0}, 604 &G0{G: testGeoPt0}, 605 "", 606 "", 607 }, 608 { 609 "geopoint invalid", 610 &G0{G: testBadGeoPt}, 611 &G0{}, 612 "invalid GeoPoint value", 613 "", 614 }, 615 { 616 "geopoint as props", 617 &G0{G: testGeoPt0}, 618 &PropertyList{ 619 Property{Name: "G", Value: testGeoPt0, NoIndex: false}, 620 }, 621 "", 622 "", 623 }, 624 { 625 "geopoint slice", 626 &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}}, 627 &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}}, 628 "", 629 "", 630 }, 631 { 632 "omit empty, all", 633 &OmitAll{}, 634 new(PropertyList), 635 "", 636 "", 637 }, 638 { 639 "omit empty", 640 &Omit{}, 641 &PropertyList{ 642 Property{Name: "St", Value: "", NoIndex: false}, 643 }, 644 "", 645 "", 646 }, 647 { 648 "omit empty, fields populated", 649 &Omit{ 650 A: "a", 651 B: 10, 652 C: true, 653 F: []int{11}, 654 }, 655 &PropertyList{ 656 Property{Name: "A", Value: "a", NoIndex: false}, 657 Property{Name: "Bb", Value: int64(10), NoIndex: false}, 658 Property{Name: "C", Value: true, NoIndex: true}, 659 Property{Name: "F", Value: []interface{}{int64(11)}, NoIndex: false}, 660 Property{Name: "St", Value: "", NoIndex: false}, 661 }, 662 "", 663 "", 664 }, 665 { 666 "omit empty, fields populated", 667 &Omit{ 668 A: "a", 669 B: 10, 670 C: true, 671 F: []int{11}, 672 S: S{St: "string"}, 673 }, 674 &PropertyList{ 675 Property{Name: "A", Value: "a", NoIndex: false}, 676 Property{Name: "Bb", Value: int64(10), NoIndex: false}, 677 Property{Name: "C", Value: true, NoIndex: true}, 678 Property{Name: "F", Value: []interface{}{int64(11)}, NoIndex: false}, 679 Property{Name: "St", Value: "string", NoIndex: false}, 680 }, 681 "", 682 "", 683 }, 684 { 685 "omit empty does not propagate", 686 &NoOmits{ 687 No: []NoOmit{ 688 {}, 689 }, 690 S: S{}, 691 Ss: S{}, 692 }, 693 &PropertyList{ 694 Property{Name: "No", Value: []interface{}{ 695 &Entity{ 696 Properties: []Property{ 697 {Name: "A", Value: "", NoIndex: false}, 698 {Name: "Bb", Value: int64(0), NoIndex: false}, 699 {Name: "C", Value: false, NoIndex: true}, 700 }, 701 }, 702 }, NoIndex: false}, 703 Property{Name: "Ss", Value: &Entity{ 704 Properties: []Property{ 705 {Name: "St", Value: "", NoIndex: false}, 706 }, 707 }, NoIndex: false}, 708 Property{Name: "St", Value: "", NoIndex: false}, 709 }, 710 "", 711 "", 712 }, 713 { 714 "key", 715 &K0{K: testKey1a}, 716 &K0{K: testKey1b}, 717 "", 718 "", 719 }, 720 { 721 "key with parent", 722 &K0{K: testKey2a}, 723 &K0{K: testKey2b}, 724 "", 725 "", 726 }, 727 { 728 "nil key", 729 &K0{}, 730 &K0{}, 731 "", 732 "", 733 }, 734 { 735 "all nil keys in slice", 736 &K1{[]*Key{nil, nil}}, 737 &K1{[]*Key{nil, nil}}, 738 "", 739 "", 740 }, 741 { 742 "some nil keys in slice", 743 &K1{[]*Key{testKey1a, nil, testKey2a}}, 744 &K1{[]*Key{testKey1b, nil, testKey2b}}, 745 "", 746 "", 747 }, 748 { 749 "overflow", 750 &O0{I: 1 << 48}, 751 &O1{}, 752 "", 753 "overflow", 754 }, 755 { 756 "time", 757 &T{T: time.Unix(1e9, 0)}, 758 &T{T: time.Unix(1e9, 0)}, 759 "", 760 "", 761 }, 762 { 763 "time as props", 764 &T{T: time.Unix(1e9, 0)}, 765 &PropertyList{ 766 Property{Name: "T", Value: time.Unix(1e9, 0), NoIndex: false}, 767 }, 768 "", 769 "", 770 }, 771 { 772 "uint save", 773 &U0{U: 1}, 774 &U0{}, 775 "unsupported struct field", 776 "", 777 }, 778 { 779 "uint load", 780 &U1{U: "not a uint"}, 781 &U0{}, 782 "", 783 "type mismatch", 784 }, 785 { 786 "zero", 787 &X0{}, 788 &X0{}, 789 "", 790 "", 791 }, 792 { 793 "basic", 794 &X0{S: "one", I: 2, i: 3}, 795 &X0{S: "one", I: 2}, 796 "", 797 "", 798 }, 799 { 800 "save string/int load myString/int32", 801 &X0{S: "one", I: 2, i: 3}, 802 &X1{S: "one", I: 2}, 803 "", 804 "", 805 }, 806 { 807 "missing fields", 808 &X0{S: "one", I: 2, i: 3}, 809 &X2{}, 810 "", 811 "no such struct field", 812 }, 813 { 814 "save string load bool", 815 &X0{S: "one", I: 2, i: 3}, 816 &X3{I: 2}, 817 "", 818 "type mismatch", 819 }, 820 { 821 "basic slice", 822 &Y0{B: true, F: []float64{7, 8, 9}}, 823 &Y0{B: true, F: []float64{7, 8, 9}}, 824 "", 825 "", 826 }, 827 { 828 "save []float64 load float64", 829 &Y0{B: true, F: []float64{7, 8, 9}}, 830 &Y1{B: true}, 831 "", 832 "requires a slice", 833 }, 834 { 835 "save []float64 load []int64", 836 &Y0{B: true, F: []float64{7, 8, 9}}, 837 &Y2{B: true}, 838 "", 839 "type mismatch", 840 }, 841 { 842 "single slice is too long", 843 &Y0{F: make([]float64, maxIndexedProperties+1)}, 844 &Y0{}, 845 "too many indexed properties", 846 "", 847 }, 848 { 849 "two slices are too long", 850 &Y0{F: make([]float64, maxIndexedProperties), G: make([]float64, maxIndexedProperties)}, 851 &Y0{}, 852 "too many indexed properties", 853 "", 854 }, 855 { 856 "one slice and one scalar are too long", 857 &Y0{F: make([]float64, maxIndexedProperties), B: true}, 858 &Y0{}, 859 "too many indexed properties", 860 "", 861 }, 862 { 863 "slice of slices of bytes", 864 &Repeated{ 865 Repeats: []Repeat{ 866 { 867 Key: "key 1", 868 Value: []byte("value 1"), 869 }, 870 { 871 Key: "key 2", 872 Value: []byte("value 2"), 873 }, 874 }, 875 }, 876 &Repeated{ 877 Repeats: []Repeat{ 878 { 879 Key: "key 1", 880 Value: []byte("value 1"), 881 }, 882 { 883 Key: "key 2", 884 Value: []byte("value 2"), 885 }, 886 }, 887 }, 888 "", 889 "", 890 }, 891 { 892 "long blob", 893 &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, 894 &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, 895 "", 896 "", 897 }, 898 { 899 "long []int8 is too long", 900 &B1{B: makeInt8Slice(maxIndexedProperties + 1)}, 901 &B1{}, 902 "too many indexed properties", 903 "", 904 }, 905 { 906 "short []int8", 907 &B1{B: makeInt8Slice(3)}, 908 &B1{B: makeInt8Slice(3)}, 909 "", 910 "", 911 }, 912 { 913 "long myBlob", 914 &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, 915 &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, 916 "", 917 "", 918 }, 919 { 920 "short myBlob", 921 &B2{B: makeUint8Slice(3)}, 922 &B2{B: makeUint8Slice(3)}, 923 "", 924 "", 925 }, 926 { 927 "long []myByte", 928 &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, 929 &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, 930 "", 931 "", 932 }, 933 { 934 "short []myByte", 935 &B3{B: makeMyByteSlice(3)}, 936 &B3{B: makeMyByteSlice(3)}, 937 "", 938 "", 939 }, 940 { 941 "slice of blobs", 942 &B4{B: [][]byte{ 943 makeUint8Slice(3), 944 makeUint8Slice(4), 945 makeUint8Slice(5), 946 }}, 947 &B4{B: [][]byte{ 948 makeUint8Slice(3), 949 makeUint8Slice(4), 950 makeUint8Slice(5), 951 }}, 952 "", 953 "", 954 }, 955 { 956 "[]byte must be noindex", 957 &PropertyList{ 958 Property{Name: "B", Value: makeUint8Slice(1501), NoIndex: false}, 959 }, 960 nil, 961 "[]byte property too long to index", 962 "", 963 }, 964 { 965 "string must be noindex", 966 &PropertyList{ 967 Property{Name: "B", Value: strings.Repeat("x", 1501), NoIndex: false}, 968 }, 969 nil, 970 "string property too long to index", 971 "", 972 }, 973 { 974 "slice of []byte must be noindex", 975 &PropertyList{ 976 Property{Name: "B", Value: []interface{}{ 977 []byte("short"), 978 makeUint8Slice(1501), 979 }, NoIndex: false}, 980 }, 981 nil, 982 "[]byte property too long to index", 983 "", 984 }, 985 { 986 "slice of string must be noindex", 987 &PropertyList{ 988 Property{Name: "B", Value: []interface{}{ 989 "short", 990 strings.Repeat("x", 1501), 991 }, NoIndex: false}, 992 }, 993 nil, 994 "string property too long to index", 995 "", 996 }, 997 { 998 "save tagged load props", 999 &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7}, 1000 &PropertyList{ 1001 // A and B are renamed to a and b; A and C are noindex, I is ignored. 1002 // Order is sorted as per byName. 1003 Property{Name: "C", Value: int64(3), NoIndex: true}, 1004 Property{Name: "D", Value: int64(4), NoIndex: false}, 1005 Property{Name: "E", Value: int64(5), NoIndex: false}, 1006 Property{Name: "J", Value: int64(7), NoIndex: true}, 1007 Property{Name: "a", Value: int64(1), NoIndex: true}, 1008 Property{Name: "b", Value: []interface{}{int64(21), int64(22), int64(23)}, NoIndex: false}, 1009 }, 1010 "", 1011 "", 1012 }, 1013 { 1014 "save tagged load tagged", 1015 &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7}, 1016 &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7}, 1017 "", 1018 "", 1019 }, 1020 { 1021 "invalid tagged1", 1022 &InvalidTagged1{I: 1}, 1023 &InvalidTagged1{}, 1024 "struct tag has invalid property name", 1025 "", 1026 }, 1027 { 1028 "invalid tagged2", 1029 &InvalidTagged2{I: 1, J: 2}, 1030 &InvalidTagged2{J: 2}, 1031 "", 1032 "", 1033 }, 1034 { 1035 "invalid tagged3", 1036 &InvalidTagged3{X: "hello"}, 1037 &InvalidTagged3{}, 1038 "struct tag has invalid property name: \"-\"", 1039 "", 1040 }, 1041 { 1042 "invalid tagged4", 1043 &InvalidTagged4{X: "hello"}, 1044 &InvalidTagged4{}, 1045 "struct tag has invalid option: \"garbage\"", 1046 "", 1047 }, 1048 { 1049 "doubler", 1050 &Doubler{S: "s", I: 1, B: true}, 1051 &Doubler{S: "ss", I: 2, B: true}, 1052 "", 1053 "", 1054 }, 1055 { 1056 "save struct load props", 1057 &X0{S: "s", I: 1}, 1058 &PropertyList{ 1059 Property{Name: "I", Value: int64(1), NoIndex: false}, 1060 Property{Name: "S", Value: "s", NoIndex: false}, 1061 }, 1062 "", 1063 "", 1064 }, 1065 { 1066 "save props load struct", 1067 &PropertyList{ 1068 Property{Name: "I", Value: int64(1), NoIndex: false}, 1069 Property{Name: "S", Value: "s", NoIndex: false}, 1070 }, 1071 &X0{S: "s", I: 1}, 1072 "", 1073 "", 1074 }, 1075 { 1076 "nil-value props", 1077 &PropertyList{ 1078 Property{Name: "I", Value: nil, NoIndex: false}, 1079 Property{Name: "B", Value: nil, NoIndex: false}, 1080 Property{Name: "S", Value: nil, NoIndex: false}, 1081 Property{Name: "F", Value: nil, NoIndex: false}, 1082 Property{Name: "K", Value: nil, NoIndex: false}, 1083 Property{Name: "T", Value: nil, NoIndex: false}, 1084 Property{Name: "J", Value: []interface{}{nil, int64(7), nil}, NoIndex: false}, 1085 }, 1086 &struct { 1087 I int64 1088 B bool 1089 S string 1090 F float64 1091 K *Key 1092 T time.Time 1093 J []int64 1094 }{ 1095 J: []int64{0, 7, 0}, 1096 }, 1097 "", 1098 "", 1099 }, 1100 { 1101 "save outer load props flatten", 1102 &OuterFlatten{ 1103 A: 1, 1104 I: []Inner1{ 1105 {10, "ten"}, 1106 {20, "twenty"}, 1107 {30, "thirty"}, 1108 }, 1109 J: Inner2{ 1110 Y: 3.14, 1111 }, 1112 Inner3: Inner3{ 1113 Z: true, 1114 }, 1115 K: Inner4{ 1116 X: Inner5{ 1117 WW: 12, 1118 }, 1119 }, 1120 L: &Inner2{ 1121 Y: 2.71, 1122 }, 1123 }, 1124 &PropertyList{ 1125 Property{Name: "A", Value: int64(1), NoIndex: false}, 1126 Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false}, 1127 Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false}, 1128 Property{Name: "J.Y", Value: float64(3.14), NoIndex: true}, 1129 Property{Name: "K.X.WW", Value: int64(12), NoIndex: false}, 1130 Property{Name: "L.Y", Value: float64(2.71), NoIndex: false}, 1131 Property{Name: "Z", Value: true, NoIndex: false}, 1132 }, 1133 "", 1134 "", 1135 }, 1136 { 1137 "load outer props flatten", 1138 &PropertyList{ 1139 Property{Name: "A", Value: int64(1), NoIndex: false}, 1140 Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false}, 1141 Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false}, 1142 Property{Name: "J.Y", Value: float64(3.14), NoIndex: true}, 1143 Property{Name: "L.Y", Value: float64(2.71), NoIndex: false}, 1144 Property{Name: "Z", Value: true, NoIndex: false}, 1145 }, 1146 &OuterFlatten{ 1147 A: 1, 1148 I: []Inner1{ 1149 {10, "ten"}, 1150 {20, "twenty"}, 1151 {30, "thirty"}, 1152 }, 1153 J: Inner2{ 1154 Y: 3.14, 1155 }, 1156 Inner3: Inner3{ 1157 Z: true, 1158 }, 1159 L: &Inner2{ 1160 Y: 2.71, 1161 }, 1162 }, 1163 "", 1164 "", 1165 }, 1166 { 1167 "save outer load props", 1168 &Outer{ 1169 A: 1, 1170 I: []Inner1{ 1171 {10, "ten"}, 1172 {20, "twenty"}, 1173 {30, "thirty"}, 1174 }, 1175 J: Inner2{ 1176 Y: 3.14, 1177 }, 1178 Inner3: Inner3{ 1179 Z: true, 1180 }, 1181 }, 1182 &PropertyList{ 1183 Property{Name: "A", Value: int64(1), NoIndex: false}, 1184 Property{Name: "I", Value: []interface{}{ 1185 &Entity{ 1186 Properties: []Property{ 1187 {Name: "W", Value: int64(10), NoIndex: false}, 1188 {Name: "X", Value: "ten", NoIndex: false}, 1189 }, 1190 }, 1191 &Entity{ 1192 Properties: []Property{ 1193 {Name: "W", Value: int64(20), NoIndex: false}, 1194 {Name: "X", Value: "twenty", NoIndex: false}, 1195 }, 1196 }, 1197 &Entity{ 1198 Properties: []Property{ 1199 {Name: "W", Value: int64(30), NoIndex: false}, 1200 {Name: "X", Value: "thirty", NoIndex: false}, 1201 }, 1202 }, 1203 }, NoIndex: false}, 1204 Property{Name: "J", Value: &Entity{ 1205 Properties: []Property{ 1206 {Name: "Y", Value: float64(3.14), NoIndex: false}, 1207 }, 1208 }, NoIndex: false}, 1209 Property{Name: "Z", Value: true, NoIndex: false}, 1210 }, 1211 "", 1212 "", 1213 }, 1214 { 1215 "save props load outer-equivalent", 1216 &PropertyList{ 1217 Property{Name: "A", Value: int64(1), NoIndex: false}, 1218 Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false}, 1219 Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false}, 1220 Property{Name: "J.Y", Value: float64(3.14), NoIndex: false}, 1221 Property{Name: "Z", Value: true, NoIndex: false}, 1222 }, 1223 &OuterEquivalent{ 1224 A: 1, 1225 IDotW: []int32{10, 20, 30}, 1226 IDotX: []string{"ten", "twenty", "thirty"}, 1227 JDotY: 3.14, 1228 Z: true, 1229 }, 1230 "", 1231 "", 1232 }, 1233 { 1234 "dotted names save", 1235 &Dotted{A: DottedA{B: DottedB{C: 88}}}, 1236 &PropertyList{ 1237 Property{Name: "A0.A1.A2", Value: &Entity{ 1238 Properties: []Property{ 1239 {Name: "B3", Value: &Entity{ 1240 Properties: []Property{ 1241 {Name: "C4.C5", Value: int64(88), NoIndex: false}, 1242 }, 1243 }, NoIndex: false}, 1244 }, 1245 }, NoIndex: false}, 1246 }, 1247 "", 1248 "", 1249 }, 1250 { 1251 "dotted names load", 1252 &PropertyList{ 1253 Property{Name: "A0.A1.A2", Value: &Entity{ 1254 Properties: []Property{ 1255 {Name: "B3", Value: &Entity{ 1256 Properties: []Property{ 1257 {Name: "C4.C5", Value: 99, NoIndex: false}, 1258 }, 1259 }, NoIndex: false}, 1260 }, 1261 }, NoIndex: false}, 1262 }, 1263 &Dotted{A: DottedA{B: DottedB{C: 99}}}, 1264 "", 1265 "", 1266 }, 1267 { 1268 "save struct load deriver", 1269 &X0{S: "s", I: 1}, 1270 &Deriver{S: "s", Derived: "derived+s"}, 1271 "", 1272 "", 1273 }, 1274 { 1275 "save deriver load struct", 1276 &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"}, 1277 &X0{S: "s"}, 1278 "", 1279 "", 1280 }, 1281 { 1282 "zero time.Time", 1283 &T{T: time.Time{}}, 1284 &T{T: time.Time{}}, 1285 "", 1286 "", 1287 }, 1288 { 1289 "time.Time near Unix zero time", 1290 &T{T: time.Unix(0, 4e3)}, 1291 &T{T: time.Unix(0, 4e3)}, 1292 "", 1293 "", 1294 }, 1295 { 1296 "time.Time, far in the future", 1297 &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, 1298 &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, 1299 "", 1300 "", 1301 }, 1302 { 1303 "time.Time, very far in the past", 1304 &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)}, 1305 &T{}, 1306 "time value out of range", 1307 "", 1308 }, 1309 { 1310 "time.Time, very far in the future", 1311 &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)}, 1312 &T{}, 1313 "time value out of range", 1314 "", 1315 }, 1316 { 1317 "structs", 1318 &N0{ 1319 X0: X0{S: "one", I: 2, i: 3}, 1320 Nonymous: X0{S: "four", I: 5, i: 6}, 1321 Ignore: "ignore", 1322 Other: "other", 1323 }, 1324 &N0{ 1325 X0: X0{S: "one", I: 2}, 1326 Nonymous: X0{S: "four", I: 5}, 1327 Other: "other", 1328 }, 1329 "", 1330 "", 1331 }, 1332 { 1333 "slice of structs", 1334 &N1{ 1335 X0: X0{S: "one", I: 2, i: 3}, 1336 Nonymous: []X0{ 1337 {S: "four", I: 5, i: 6}, 1338 {S: "seven", I: 8, i: 9}, 1339 {S: "ten", I: 11, i: 12}, 1340 {S: "thirteen", I: 14, i: 15}, 1341 }, 1342 Ignore: "ignore", 1343 Other: "other", 1344 }, 1345 &N1{ 1346 X0: X0{S: "one", I: 2}, 1347 Nonymous: []X0{ 1348 {S: "four", I: 5}, 1349 {S: "seven", I: 8}, 1350 {S: "ten", I: 11}, 1351 {S: "thirteen", I: 14}, 1352 }, 1353 Other: "other", 1354 }, 1355 "", 1356 "", 1357 }, 1358 { 1359 "structs with slices of structs", 1360 &N2{ 1361 N1: N1{ 1362 X0: X0{S: "rouge"}, 1363 Nonymous: []X0{ 1364 {S: "rosso0"}, 1365 {S: "rosso1"}, 1366 }, 1367 }, 1368 Green: N1{ 1369 X0: X0{S: "vert"}, 1370 Nonymous: []X0{ 1371 {S: "verde0"}, 1372 {S: "verde1"}, 1373 {S: "verde2"}, 1374 }, 1375 }, 1376 Blue: N1{ 1377 X0: X0{S: "bleu"}, 1378 Nonymous: []X0{ 1379 {S: "blu0"}, 1380 {S: "blu1"}, 1381 {S: "blu2"}, 1382 {S: "blu3"}, 1383 }, 1384 }, 1385 }, 1386 &N2{ 1387 N1: N1{ 1388 X0: X0{S: "rouge"}, 1389 Nonymous: []X0{ 1390 {S: "rosso0"}, 1391 {S: "rosso1"}, 1392 }, 1393 }, 1394 Green: N1{ 1395 X0: X0{S: "vert"}, 1396 Nonymous: []X0{ 1397 {S: "verde0"}, 1398 {S: "verde1"}, 1399 {S: "verde2"}, 1400 }, 1401 }, 1402 Blue: N1{ 1403 X0: X0{S: "bleu"}, 1404 Nonymous: []X0{ 1405 {S: "blu0"}, 1406 {S: "blu1"}, 1407 {S: "blu2"}, 1408 {S: "blu3"}, 1409 }, 1410 }, 1411 }, 1412 "", 1413 "", 1414 }, 1415 { 1416 "save structs load props", 1417 &N2{ 1418 N1: N1{ 1419 X0: X0{S: "rouge"}, 1420 Nonymous: []X0{ 1421 {S: "rosso0"}, 1422 {S: "rosso1"}, 1423 }, 1424 }, 1425 Green: N1{ 1426 X0: X0{S: "vert"}, 1427 Nonymous: []X0{ 1428 {S: "verde0"}, 1429 {S: "verde1"}, 1430 {S: "verde2"}, 1431 }, 1432 }, 1433 Blue: N1{ 1434 X0: X0{S: "bleu"}, 1435 Nonymous: []X0{ 1436 {S: "blu0"}, 1437 {S: "blu1"}, 1438 {S: "blu2"}, 1439 {S: "blu3"}, 1440 }, 1441 }, 1442 }, 1443 &PropertyList{ 1444 Property{Name: "Blue", Value: &Entity{ 1445 Properties: []Property{ 1446 {Name: "I", Value: int64(0), NoIndex: false}, 1447 {Name: "Nonymous", Value: []interface{}{ 1448 &Entity{ 1449 Properties: []Property{ 1450 {Name: "I", Value: int64(0), NoIndex: false}, 1451 {Name: "S", Value: "blu0", NoIndex: false}, 1452 }, 1453 }, 1454 &Entity{ 1455 Properties: []Property{ 1456 {Name: "I", Value: int64(0), NoIndex: false}, 1457 {Name: "S", Value: "blu1", NoIndex: false}, 1458 }, 1459 }, 1460 &Entity{ 1461 Properties: []Property{ 1462 {Name: "I", Value: int64(0), NoIndex: false}, 1463 {Name: "S", Value: "blu2", NoIndex: false}, 1464 }, 1465 }, 1466 &Entity{ 1467 Properties: []Property{ 1468 {Name: "I", Value: int64(0), NoIndex: false}, 1469 {Name: "S", Value: "blu3", NoIndex: false}, 1470 }, 1471 }, 1472 }, NoIndex: false}, 1473 {Name: "Other", Value: "", NoIndex: false}, 1474 {Name: "S", Value: "bleu", NoIndex: false}, 1475 }, 1476 }, NoIndex: false}, 1477 Property{Name: "green", Value: &Entity{ 1478 Properties: []Property{ 1479 {Name: "I", Value: int64(0), NoIndex: false}, 1480 {Name: "Nonymous", Value: []interface{}{ 1481 &Entity{ 1482 Properties: []Property{ 1483 {Name: "I", Value: int64(0), NoIndex: false}, 1484 {Name: "S", Value: "verde0", NoIndex: false}, 1485 }, 1486 }, 1487 &Entity{ 1488 Properties: []Property{ 1489 {Name: "I", Value: int64(0), NoIndex: false}, 1490 {Name: "S", Value: "verde1", NoIndex: false}, 1491 }, 1492 }, 1493 &Entity{ 1494 Properties: []Property{ 1495 {Name: "I", Value: int64(0), NoIndex: false}, 1496 {Name: "S", Value: "verde2", NoIndex: false}, 1497 }, 1498 }, 1499 }, NoIndex: false}, 1500 {Name: "Other", Value: "", NoIndex: false}, 1501 {Name: "S", Value: "vert", NoIndex: false}, 1502 }, 1503 }, NoIndex: false}, 1504 Property{Name: "red", Value: &Entity{ 1505 Properties: []Property{ 1506 {Name: "I", Value: int64(0), NoIndex: false}, 1507 {Name: "Nonymous", Value: []interface{}{ 1508 &Entity{ 1509 Properties: []Property{ 1510 {Name: "I", Value: int64(0), NoIndex: false}, 1511 {Name: "S", Value: "rosso0", NoIndex: false}, 1512 }, 1513 }, 1514 &Entity{ 1515 Properties: []Property{ 1516 {Name: "I", Value: int64(0), NoIndex: false}, 1517 {Name: "S", Value: "rosso1", NoIndex: false}, 1518 }, 1519 }, 1520 }, NoIndex: false}, 1521 {Name: "Other", Value: "", NoIndex: false}, 1522 {Name: "S", Value: "rouge", NoIndex: false}, 1523 }, 1524 }, NoIndex: false}, 1525 }, 1526 "", 1527 "", 1528 }, 1529 { 1530 "nested entity with key", 1531 &WithNestedEntityWithKey{ 1532 N: EntityWithKey{ 1533 I: 12, 1534 S: "abcd", 1535 K: testKey0, 1536 }, 1537 }, 1538 &WithNestedEntityWithKey{ 1539 N: EntityWithKey{ 1540 I: 12, 1541 S: "abcd", 1542 K: testKey0, 1543 }, 1544 }, 1545 "", 1546 "", 1547 }, 1548 { 1549 "entity with key at top level", 1550 &EntityWithKey{ 1551 I: 12, 1552 S: "abc", 1553 K: testKey0, 1554 }, 1555 &EntityWithKey{ 1556 I: 12, 1557 S: "abc", 1558 K: testKey0, 1559 }, 1560 "", 1561 "", 1562 }, 1563 { 1564 "entity with key at top level (key is populated on load)", 1565 &EntityWithKey{ 1566 I: 12, 1567 S: "abc", 1568 }, 1569 &EntityWithKey{ 1570 I: 12, 1571 S: "abc", 1572 K: testKey0, 1573 }, 1574 "", 1575 "", 1576 }, 1577 { 1578 "__key__ field not a *Key", 1579 &NestedWithNonKeyField{ 1580 N: WithNonKeyField{ 1581 I: 12, 1582 K: "abcd", 1583 }, 1584 }, 1585 &NestedWithNonKeyField{ 1586 N: WithNonKeyField{ 1587 I: 12, 1588 K: "abcd", 1589 }, 1590 }, 1591 "datastore: __key__ field on struct datastore.WithNonKeyField is not a *datastore.Key", 1592 "", 1593 }, 1594 { 1595 "save struct with ptr to struct fields", 1596 &PtrToStructField{ 1597 &Basic{ 1598 A: "b", 1599 }, 1600 &Basic{ 1601 A: "c", 1602 }, 1603 &Basic{ 1604 A: "anon", 1605 }, 1606 []*Basic{ 1607 { 1608 A: "slice0", 1609 }, 1610 { 1611 A: "slice1", 1612 }, 1613 }, 1614 }, 1615 &PropertyList{ 1616 Property{Name: "A", Value: "anon", NoIndex: false}, 1617 Property{Name: "B", Value: &Entity{ 1618 Properties: []Property{ 1619 {Name: "A", Value: "b", NoIndex: false}, 1620 }, 1621 }}, 1622 Property{Name: "D", Value: []interface{}{ 1623 &Entity{ 1624 Properties: []Property{ 1625 {Name: "A", Value: "slice0", NoIndex: false}, 1626 }, 1627 }, 1628 &Entity{ 1629 Properties: []Property{ 1630 {Name: "A", Value: "slice1", NoIndex: false}, 1631 }, 1632 }, 1633 }, NoIndex: false}, 1634 Property{Name: "c", Value: &Entity{ 1635 Properties: []Property{ 1636 {Name: "A", Value: "c", NoIndex: true}, 1637 }, 1638 }, NoIndex: true}, 1639 }, 1640 "", 1641 "", 1642 }, 1643 { 1644 "save and load struct with ptr to struct fields", 1645 &PtrToStructField{ 1646 &Basic{ 1647 A: "b", 1648 }, 1649 &Basic{ 1650 A: "c", 1651 }, 1652 &Basic{ 1653 A: "anon", 1654 }, 1655 []*Basic{ 1656 { 1657 A: "slice0", 1658 }, 1659 { 1660 A: "slice1", 1661 }, 1662 }, 1663 }, 1664 &PtrToStructField{ 1665 &Basic{ 1666 A: "b", 1667 }, 1668 &Basic{ 1669 A: "c", 1670 }, 1671 &Basic{ 1672 A: "anon", 1673 }, 1674 []*Basic{ 1675 { 1676 A: "slice0", 1677 }, 1678 { 1679 A: "slice1", 1680 }, 1681 }, 1682 }, 1683 "", 1684 "", 1685 }, 1686 { 1687 "struct with nil ptr to struct fields", 1688 &PtrToStructField{ 1689 nil, 1690 nil, 1691 nil, 1692 nil, 1693 }, 1694 new(PropertyList), 1695 "", 1696 "", 1697 }, 1698 { 1699 "nested load entity with key", 1700 &WithNestedEntityWithKey{ 1701 N: EntityWithKey{ 1702 I: 12, 1703 S: "abcd", 1704 K: testKey0, 1705 }, 1706 }, 1707 &PropertyList{ 1708 Property{Name: "N", Value: &Entity{ 1709 Key: testKey0, 1710 Properties: []Property{ 1711 {Name: "I", Value: int64(12), NoIndex: false}, 1712 {Name: "S", Value: "abcd", NoIndex: false}, 1713 }, 1714 }, 1715 NoIndex: false}, 1716 }, 1717 "", 1718 "", 1719 }, 1720 { 1721 "nested save entity with key", 1722 &PropertyList{ 1723 Property{Name: "N", Value: &Entity{ 1724 Key: testKey0, 1725 Properties: []Property{ 1726 {Name: "I", Value: int64(12), NoIndex: false}, 1727 {Name: "S", Value: "abcd", NoIndex: false}, 1728 }, 1729 }, NoIndex: false}, 1730 }, 1731 1732 &WithNestedEntityWithKey{ 1733 N: EntityWithKey{ 1734 I: 12, 1735 S: "abcd", 1736 K: testKey0, 1737 }, 1738 }, 1739 "", 1740 "", 1741 }, 1742 { 1743 "anonymous field with tag", 1744 &N3{ 1745 C3: C3{C: "s"}, 1746 }, 1747 &PropertyList{ 1748 Property{Name: "red", Value: &Entity{ 1749 Properties: []Property{ 1750 {Name: "C", Value: "s", NoIndex: false}, 1751 }, 1752 }, NoIndex: false}, 1753 }, 1754 "", 1755 "", 1756 }, 1757 { 1758 "unexported anonymous field", 1759 &N4{ 1760 c4: c4{C: "s"}, 1761 }, 1762 &PropertyList{ 1763 Property{Name: "C", Value: "s", NoIndex: false}, 1764 }, 1765 "", 1766 "", 1767 }, 1768 { 1769 "unexported anonymous field with tag", 1770 &N5{ 1771 c4: c4{C: "s"}, 1772 }, 1773 new(PropertyList), 1774 "", 1775 "", 1776 }, 1777 { 1778 "save props load structs with ragged fields", 1779 &PropertyList{ 1780 Property{Name: "red.S", Value: "rot", NoIndex: false}, 1781 Property{Name: "green.Nonymous.I", Value: []interface{}{int64(10), int64(11), int64(12), int64(13)}, NoIndex: false}, 1782 Property{Name: "Blue.Nonymous.I", Value: []interface{}{int64(20), int64(21)}, NoIndex: false}, 1783 Property{Name: "Blue.Nonymous.S", Value: []interface{}{"blau0", "blau1", "blau2"}, NoIndex: false}, 1784 }, 1785 &N2{ 1786 N1: N1{ 1787 X0: X0{S: "rot"}, 1788 }, 1789 Green: N1{ 1790 Nonymous: []X0{ 1791 {I: 10}, 1792 {I: 11}, 1793 {I: 12}, 1794 {I: 13}, 1795 }, 1796 }, 1797 Blue: N1{ 1798 Nonymous: []X0{ 1799 {S: "blau0", I: 20}, 1800 {S: "blau1", I: 21}, 1801 {S: "blau2"}, 1802 }, 1803 }, 1804 }, 1805 "", 1806 "", 1807 }, 1808 { 1809 "save structs with noindex tags", 1810 &struct { 1811 A struct { 1812 X string `datastore:",noindex"` 1813 Y string 1814 } `datastore:",noindex"` 1815 B struct { 1816 X string `datastore:",noindex"` 1817 Y string 1818 } 1819 }{}, 1820 &PropertyList{ 1821 Property{Name: "A", Value: &Entity{ 1822 Properties: []Property{ 1823 {Name: "X", Value: "", NoIndex: true}, 1824 {Name: "Y", Value: "", NoIndex: true}, 1825 }, 1826 }, NoIndex: true}, 1827 Property{Name: "B", Value: &Entity{ 1828 Properties: []Property{ 1829 {Name: "X", Value: "", NoIndex: true}, 1830 {Name: "Y", Value: "", NoIndex: false}, 1831 }, 1832 }, NoIndex: false}, 1833 }, 1834 "", 1835 "", 1836 }, 1837 { 1838 "embedded struct with name override", 1839 &struct { 1840 Inner1 `datastore:"foo"` 1841 }{}, 1842 &PropertyList{ 1843 Property{Name: "foo", Value: &Entity{ 1844 Properties: []Property{ 1845 {Name: "W", Value: int64(0), NoIndex: false}, 1846 {Name: "X", Value: "", NoIndex: false}, 1847 }, 1848 }, NoIndex: false}, 1849 }, 1850 "", 1851 "", 1852 }, 1853 { 1854 "slice of slices", 1855 &SliceOfSlices{}, 1856 nil, 1857 "flattening nested structs leads to a slice of slices", 1858 "", 1859 }, 1860 { 1861 "recursive struct", 1862 &Recursive{}, 1863 &Recursive{}, 1864 "", 1865 "", 1866 }, 1867 { 1868 "mutually recursive struct", 1869 &MutuallyRecursive0{}, 1870 &MutuallyRecursive0{}, 1871 "", 1872 "", 1873 }, 1874 { 1875 "non-exported struct fields", 1876 &struct { 1877 i, J int64 1878 }{i: 1, J: 2}, 1879 &PropertyList{ 1880 Property{Name: "J", Value: int64(2), NoIndex: false}, 1881 }, 1882 "", 1883 "", 1884 }, 1885 { 1886 "json.RawMessage", 1887 &struct { 1888 J json.RawMessage 1889 }{ 1890 J: json.RawMessage("rawr"), 1891 }, 1892 &PropertyList{ 1893 Property{Name: "J", Value: []byte("rawr"), NoIndex: false}, 1894 }, 1895 "", 1896 "", 1897 }, 1898 { 1899 "json.RawMessage to myBlob", 1900 &struct { 1901 B json.RawMessage 1902 }{ 1903 B: json.RawMessage("rawr"), 1904 }, 1905 &B2{B: myBlob("rawr")}, 1906 "", 1907 "", 1908 }, 1909 { 1910 "repeated property names", 1911 &PropertyList{ 1912 Property{Name: "A", Value: ""}, 1913 Property{Name: "A", Value: ""}, 1914 }, 1915 nil, 1916 "duplicate Property", 1917 "", 1918 }, 1919 { 1920 "embedded time field", 1921 &SpecialTime{MyTime: EmbeddedTime{ts}}, 1922 &SpecialTime{MyTime: EmbeddedTime{ts}}, 1923 "", 1924 "", 1925 }, 1926 { 1927 "embedded time load", 1928 &PropertyList{ 1929 Property{Name: "MyTime.Time", Value: ts}, 1930 }, 1931 &SpecialTime{MyTime: EmbeddedTime{ts}}, 1932 "", 1933 "", 1934 }, 1935 { 1936 "pointer fields: nil", 1937 &Pointers{}, 1938 &Pointers{}, 1939 "", 1940 "", 1941 }, 1942 { 1943 "pointer fields: populated with zeroes", 1944 populatedPointers(), 1945 populatedPointers(), 1946 "", 1947 "", 1948 }, 1949} 1950 1951// checkErr returns the empty string if either both want and err are zero, 1952// or if want is a non-empty substring of err's string representation. 1953func checkErr(want string, err error) string { 1954 if err != nil { 1955 got := err.Error() 1956 if want == "" || !strings.Contains(got, want) { 1957 return got 1958 } 1959 } else if want != "" { 1960 return fmt.Sprintf("want error %q", want) 1961 } 1962 return "" 1963} 1964 1965func TestRoundTrip(t *testing.T) { 1966 for _, tc := range testCases { 1967 p, err := saveEntity(testKey0, tc.src) 1968 if s := checkErr(tc.putErr, err); s != "" { 1969 t.Errorf("%s: save: %s", tc.desc, s) 1970 continue 1971 } 1972 if p == nil { 1973 continue 1974 } 1975 var got interface{} 1976 if _, ok := tc.want.(*PropertyList); ok { 1977 got = new(PropertyList) 1978 } else { 1979 got = reflect.New(reflect.TypeOf(tc.want).Elem()).Interface() 1980 } 1981 err = loadEntityProto(got, p) 1982 if s := checkErr(tc.getErr, err); s != "" { 1983 t.Errorf("%s: load: %s", tc.desc, s) 1984 continue 1985 } 1986 if pl, ok := got.(*PropertyList); ok { 1987 // Sort by name to make sure we have a deterministic order. 1988 sortPL(*pl) 1989 } 1990 1991 if !testutil.Equal(got, tc.want, cmp.AllowUnexported(X0{}, X2{})) { 1992 t.Errorf("%s: compare:\ngot: %+#v\nwant: %+#v", tc.desc, got, tc.want) 1993 continue 1994 } 1995 } 1996} 1997 1998type aPtrPLS struct { 1999 Count int 2000} 2001 2002func (pls *aPtrPLS) Load([]Property) error { 2003 pls.Count++ 2004 return nil 2005} 2006 2007func (pls *aPtrPLS) Save() ([]Property, error) { 2008 return []Property{{Name: "Count", Value: 4}}, nil 2009} 2010 2011type aValuePLS struct { 2012 Count int 2013} 2014 2015func (pls aValuePLS) Load([]Property) error { 2016 pls.Count += 2 2017 return nil 2018} 2019 2020func (pls aValuePLS) Save() ([]Property, error) { 2021 return []Property{{Name: "Count", Value: 8}}, nil 2022} 2023 2024type aValuePtrPLS struct { 2025 Count int 2026} 2027 2028func (pls *aValuePtrPLS) Load([]Property) error { 2029 pls.Count = 11 2030 return nil 2031} 2032 2033func (pls *aValuePtrPLS) Save() ([]Property, error) { 2034 return []Property{{Name: "Count", Value: 12}}, nil 2035} 2036 2037type aNotPLS struct { 2038 Count int 2039} 2040 2041type plsString string 2042 2043func (s *plsString) Load([]Property) error { 2044 *s = "LOADED" 2045 return nil 2046} 2047 2048func (s *plsString) Save() ([]Property, error) { 2049 return []Property{{Name: "SS", Value: "SAVED"}}, nil 2050} 2051 2052func ptrToplsString(s string) *plsString { 2053 plsStr := plsString(s) 2054 return &plsStr 2055} 2056 2057type aSubPLS struct { 2058 Foo string 2059 Bar *aPtrPLS 2060 Baz aValuePtrPLS 2061 S plsString 2062} 2063 2064type aSubNotPLS struct { 2065 Foo string 2066 Bar *aNotPLS 2067} 2068 2069type aSubPLSErr struct { 2070 Foo string 2071 Bar aValuePLS 2072} 2073 2074type aSubPLSNoErr struct { 2075 Foo string 2076 Bar aPtrPLS 2077} 2078 2079type GrandparentFlatten struct { 2080 Parent Parent `datastore:",flatten"` 2081} 2082 2083type GrandparentOfPtrFlatten struct { 2084 Parent ParentOfPtr `datastore:",flatten"` 2085} 2086 2087type GrandparentOfSlice struct { 2088 Parent ParentOfSlice 2089} 2090 2091type GrandparentOfSlicePtrs struct { 2092 Parent ParentOfSlicePtrs 2093} 2094 2095type GrandparentOfSliceFlatten struct { 2096 Parent ParentOfSlice `datastore:",flatten"` 2097} 2098 2099type GrandparentOfSlicePtrsFlatten struct { 2100 Parent ParentOfSlicePtrs `datastore:",flatten"` 2101} 2102 2103type Grandparent struct { 2104 Parent Parent 2105} 2106 2107type Parent struct { 2108 Child Child 2109 String plsString 2110} 2111 2112type ParentOfPtr struct { 2113 Child *Child 2114 String *plsString 2115} 2116 2117type ParentOfSlice struct { 2118 Children []Child 2119 Strings []plsString 2120} 2121 2122type ParentOfSlicePtrs struct { 2123 Children []*Child 2124 Strings []*plsString 2125} 2126 2127type Child struct { 2128 I int 2129 Grandchild Grandchild 2130} 2131 2132type Grandchild struct { 2133 S string 2134} 2135 2136func (c *Child) Load(props []Property) error { 2137 for _, p := range props { 2138 if p.Name == "I" { 2139 c.I++ 2140 } else if p.Name == "Grandchild.S" { 2141 c.Grandchild.S = "grandchild loaded" 2142 } 2143 } 2144 2145 return nil 2146} 2147 2148func (c *Child) Save() ([]Property, error) { 2149 v := c.I + 1 2150 return []Property{ 2151 {Name: "I", Value: v}, 2152 {Name: "Grandchild.S", Value: fmt.Sprintf("grandchild saved %d", v)}, 2153 }, nil 2154} 2155 2156func TestLoadSavePLS(t *testing.T) { 2157 type testCase struct { 2158 desc string 2159 src interface{} 2160 wantSave *pb.Entity 2161 wantLoad interface{} 2162 saveErr string 2163 loadErr string 2164 } 2165 2166 testCases := []testCase{ 2167 { 2168 desc: "non-struct implements PLS (top-level)", 2169 src: ptrToplsString("hello"), 2170 wantSave: &pb.Entity{ 2171 Key: keyToProto(testKey0), 2172 Properties: map[string]*pb.Value{ 2173 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2174 }, 2175 }, 2176 wantLoad: ptrToplsString("LOADED"), 2177 }, 2178 { 2179 desc: "substructs do implement PLS", 2180 src: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 2}, Baz: aValuePtrPLS{Count: 15}, S: "something"}, 2181 wantSave: &pb.Entity{ 2182 Key: keyToProto(testKey0), 2183 Properties: map[string]*pb.Value{ 2184 "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, 2185 "Bar": {ValueType: &pb.Value_EntityValue{ 2186 EntityValue: &pb.Entity{ 2187 Properties: map[string]*pb.Value{ 2188 "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}}, 2189 }, 2190 }, 2191 }}, 2192 "Baz": {ValueType: &pb.Value_EntityValue{ 2193 EntityValue: &pb.Entity{ 2194 Properties: map[string]*pb.Value{ 2195 "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}}, 2196 }, 2197 }, 2198 }}, 2199 "S": {ValueType: &pb.Value_EntityValue{ 2200 EntityValue: &pb.Entity{ 2201 Properties: map[string]*pb.Value{ 2202 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2203 }, 2204 }, 2205 }}, 2206 }, 2207 }, 2208 wantLoad: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 1}, Baz: aValuePtrPLS{Count: 11}, S: "LOADED"}, 2209 }, 2210 { 2211 desc: "substruct (ptr) does implement PLS, nil valued substruct", 2212 src: &aSubPLS{Foo: "foo", S: "something"}, 2213 wantSave: &pb.Entity{ 2214 Key: keyToProto(testKey0), 2215 Properties: map[string]*pb.Value{ 2216 "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, 2217 "Baz": {ValueType: &pb.Value_EntityValue{ 2218 EntityValue: &pb.Entity{ 2219 Properties: map[string]*pb.Value{ 2220 "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}}, 2221 }, 2222 }, 2223 }}, 2224 "S": {ValueType: &pb.Value_EntityValue{ 2225 EntityValue: &pb.Entity{ 2226 Properties: map[string]*pb.Value{ 2227 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2228 }, 2229 }, 2230 }}, 2231 }, 2232 }, 2233 wantLoad: &aSubPLS{Foo: "foo", Baz: aValuePtrPLS{Count: 11}, S: "LOADED"}, 2234 }, 2235 { 2236 desc: "substruct (ptr) does not implement PLS", 2237 src: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}}, 2238 wantSave: &pb.Entity{ 2239 Key: keyToProto(testKey0), 2240 Properties: map[string]*pb.Value{ 2241 "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, 2242 "Bar": {ValueType: &pb.Value_EntityValue{ 2243 EntityValue: &pb.Entity{ 2244 Properties: map[string]*pb.Value{ 2245 "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, 2246 }, 2247 }, 2248 }}, 2249 }, 2250 }, 2251 wantLoad: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}}, 2252 }, 2253 { 2254 desc: "substruct (value) does implement PLS, error on save", 2255 src: &aSubPLSErr{Foo: "foo", Bar: aValuePLS{Count: 2}}, 2256 wantSave: (*pb.Entity)(nil), 2257 wantLoad: &aSubPLSErr{}, 2258 saveErr: "PropertyLoadSaver methods must be implemented on a pointer", 2259 }, 2260 { 2261 desc: "substruct (value) does implement PLS, error on load", 2262 src: &aSubPLSNoErr{Foo: "foo", Bar: aPtrPLS{Count: 2}}, 2263 wantSave: &pb.Entity{ 2264 Key: keyToProto(testKey0), 2265 Properties: map[string]*pb.Value{ 2266 "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, 2267 "Bar": {ValueType: &pb.Value_EntityValue{ 2268 EntityValue: &pb.Entity{ 2269 Properties: map[string]*pb.Value{ 2270 "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}}, 2271 }, 2272 }, 2273 }}, 2274 }, 2275 }, 2276 wantLoad: &aSubPLSErr{}, 2277 loadErr: "PropertyLoadSaver methods must be implemented on a pointer", 2278 }, 2279 2280 { 2281 desc: "parent does not have flatten option, child impl PLS", 2282 src: &Grandparent{ 2283 Parent: Parent{ 2284 Child: Child{ 2285 I: 9, 2286 Grandchild: Grandchild{ 2287 S: "BAD", 2288 }, 2289 }, 2290 String: plsString("something"), 2291 }, 2292 }, 2293 wantSave: &pb.Entity{ 2294 Key: keyToProto(testKey0), 2295 Properties: map[string]*pb.Value{ 2296 "Parent": {ValueType: &pb.Value_EntityValue{ 2297 EntityValue: &pb.Entity{ 2298 Properties: map[string]*pb.Value{ 2299 "Child": {ValueType: &pb.Value_EntityValue{ 2300 EntityValue: &pb.Entity{ 2301 Properties: map[string]*pb.Value{ 2302 "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, 2303 "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, 2304 }, 2305 }, 2306 }}, 2307 "String": {ValueType: &pb.Value_EntityValue{ 2308 EntityValue: &pb.Entity{ 2309 Properties: map[string]*pb.Value{ 2310 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2311 }, 2312 }, 2313 }}, 2314 }, 2315 }, 2316 }}, 2317 }, 2318 }, 2319 wantLoad: &Grandparent{ 2320 Parent: Parent{ 2321 Child: Child{ 2322 I: 1, 2323 Grandchild: Grandchild{ 2324 S: "grandchild loaded", 2325 }, 2326 }, 2327 String: "LOADED", 2328 }, 2329 }, 2330 }, 2331 { 2332 desc: "parent has flatten option enabled, child impl PLS", 2333 src: &GrandparentFlatten{ 2334 Parent: Parent{ 2335 Child: Child{ 2336 I: 7, 2337 Grandchild: Grandchild{ 2338 S: "BAD", 2339 }, 2340 }, 2341 String: plsString("something"), 2342 }, 2343 }, 2344 wantSave: &pb.Entity{ 2345 Key: keyToProto(testKey0), 2346 Properties: map[string]*pb.Value{ 2347 "Parent.Child.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, 2348 "Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, 2349 "Parent.String.SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2350 }, 2351 }, 2352 wantLoad: &GrandparentFlatten{ 2353 Parent: Parent{ 2354 Child: Child{ 2355 I: 1, 2356 Grandchild: Grandchild{ 2357 S: "grandchild loaded", 2358 }, 2359 }, 2360 String: "LOADED", 2361 }, 2362 }, 2363 }, 2364 2365 { 2366 desc: "parent has flatten option enabled, child (ptr to) impl PLS", 2367 src: &GrandparentOfPtrFlatten{ 2368 Parent: ParentOfPtr{ 2369 Child: &Child{ 2370 I: 7, 2371 Grandchild: Grandchild{ 2372 S: "BAD", 2373 }, 2374 }, 2375 String: ptrToplsString("something"), 2376 }, 2377 }, 2378 wantSave: &pb.Entity{ 2379 Key: keyToProto(testKey0), 2380 Properties: map[string]*pb.Value{ 2381 "Parent.Child.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, 2382 "Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, 2383 "Parent.String.SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2384 }, 2385 }, 2386 wantLoad: &GrandparentOfPtrFlatten{ 2387 Parent: ParentOfPtr{ 2388 Child: &Child{ 2389 I: 1, 2390 Grandchild: Grandchild{ 2391 S: "grandchild loaded", 2392 }, 2393 }, 2394 String: ptrToplsString("LOADED"), 2395 }, 2396 }, 2397 }, 2398 { 2399 desc: "children (slice of) impl PLS", 2400 src: &GrandparentOfSlice{ 2401 Parent: ParentOfSlice{ 2402 Children: []Child{ 2403 { 2404 I: 7, 2405 Grandchild: Grandchild{ 2406 S: "BAD", 2407 }, 2408 }, 2409 { 2410 I: 9, 2411 Grandchild: Grandchild{ 2412 S: "BAD2", 2413 }, 2414 }, 2415 }, 2416 Strings: []plsString{ 2417 "something1", 2418 "something2", 2419 }, 2420 }, 2421 }, 2422 wantSave: &pb.Entity{ 2423 Key: keyToProto(testKey0), 2424 Properties: map[string]*pb.Value{ 2425 "Parent": {ValueType: &pb.Value_EntityValue{ 2426 EntityValue: &pb.Entity{ 2427 Properties: map[string]*pb.Value{ 2428 "Children": {ValueType: &pb.Value_ArrayValue{ 2429 ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ 2430 {ValueType: &pb.Value_EntityValue{ 2431 EntityValue: &pb.Entity{ 2432 Properties: map[string]*pb.Value{ 2433 "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, 2434 "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, 2435 }, 2436 }, 2437 }}, 2438 {ValueType: &pb.Value_EntityValue{ 2439 EntityValue: &pb.Entity{ 2440 Properties: map[string]*pb.Value{ 2441 "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, 2442 "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, 2443 }, 2444 }, 2445 }}, 2446 }}, 2447 }}, 2448 "Strings": {ValueType: &pb.Value_ArrayValue{ 2449 ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ 2450 {ValueType: &pb.Value_EntityValue{ 2451 EntityValue: &pb.Entity{ 2452 Properties: map[string]*pb.Value{ 2453 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2454 }, 2455 }, 2456 }}, 2457 {ValueType: &pb.Value_EntityValue{ 2458 EntityValue: &pb.Entity{ 2459 Properties: map[string]*pb.Value{ 2460 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2461 }, 2462 }, 2463 }}, 2464 }}, 2465 }}, 2466 }, 2467 }, 2468 }}, 2469 }, 2470 }, 2471 wantLoad: &GrandparentOfSlice{ 2472 Parent: ParentOfSlice{ 2473 Children: []Child{ 2474 { 2475 I: 1, 2476 Grandchild: Grandchild{ 2477 S: "grandchild loaded", 2478 }, 2479 }, 2480 { 2481 I: 1, 2482 Grandchild: Grandchild{ 2483 S: "grandchild loaded", 2484 }, 2485 }, 2486 }, 2487 Strings: []plsString{ 2488 "LOADED", 2489 "LOADED", 2490 }, 2491 }, 2492 }, 2493 }, 2494 { 2495 desc: "children (slice of ptrs) impl PLS", 2496 src: &GrandparentOfSlicePtrs{ 2497 Parent: ParentOfSlicePtrs{ 2498 Children: []*Child{ 2499 { 2500 I: 7, 2501 Grandchild: Grandchild{ 2502 S: "BAD", 2503 }, 2504 }, 2505 { 2506 I: 9, 2507 Grandchild: Grandchild{ 2508 S: "BAD2", 2509 }, 2510 }, 2511 }, 2512 Strings: []*plsString{ 2513 ptrToplsString("something1"), 2514 ptrToplsString("something2"), 2515 }, 2516 }, 2517 }, 2518 wantSave: &pb.Entity{ 2519 Key: keyToProto(testKey0), 2520 Properties: map[string]*pb.Value{ 2521 "Parent": {ValueType: &pb.Value_EntityValue{ 2522 EntityValue: &pb.Entity{ 2523 Properties: map[string]*pb.Value{ 2524 "Children": {ValueType: &pb.Value_ArrayValue{ 2525 ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ 2526 {ValueType: &pb.Value_EntityValue{ 2527 EntityValue: &pb.Entity{ 2528 Properties: map[string]*pb.Value{ 2529 "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, 2530 "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, 2531 }, 2532 }, 2533 }}, 2534 {ValueType: &pb.Value_EntityValue{ 2535 EntityValue: &pb.Entity{ 2536 Properties: map[string]*pb.Value{ 2537 "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, 2538 "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, 2539 }, 2540 }, 2541 }}, 2542 }}, 2543 }}, 2544 "Strings": {ValueType: &pb.Value_ArrayValue{ 2545 ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ 2546 {ValueType: &pb.Value_EntityValue{ 2547 EntityValue: &pb.Entity{ 2548 Properties: map[string]*pb.Value{ 2549 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2550 }, 2551 }, 2552 }}, 2553 {ValueType: &pb.Value_EntityValue{ 2554 EntityValue: &pb.Entity{ 2555 Properties: map[string]*pb.Value{ 2556 "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2557 }, 2558 }, 2559 }}, 2560 }}, 2561 }}, 2562 }, 2563 }, 2564 }}, 2565 }, 2566 }, 2567 wantLoad: &GrandparentOfSlicePtrs{ 2568 Parent: ParentOfSlicePtrs{ 2569 Children: []*Child{ 2570 { 2571 I: 1, 2572 Grandchild: Grandchild{ 2573 S: "grandchild loaded", 2574 }, 2575 }, 2576 { 2577 I: 1, 2578 Grandchild: Grandchild{ 2579 S: "grandchild loaded", 2580 }, 2581 }, 2582 }, 2583 Strings: []*plsString{ 2584 ptrToplsString("LOADED"), 2585 ptrToplsString("LOADED"), 2586 }, 2587 }, 2588 }, 2589 }, 2590 { 2591 desc: "parent has flatten option, children (slice of) impl PLS", 2592 src: &GrandparentOfSliceFlatten{ 2593 Parent: ParentOfSlice{ 2594 Children: []Child{ 2595 { 2596 I: 7, 2597 Grandchild: Grandchild{ 2598 S: "BAD", 2599 }, 2600 }, 2601 { 2602 I: 9, 2603 Grandchild: Grandchild{ 2604 S: "BAD2", 2605 }, 2606 }, 2607 }, 2608 Strings: []plsString{ 2609 "something1", 2610 "something2", 2611 }, 2612 }, 2613 }, 2614 wantSave: &pb.Entity{ 2615 Key: keyToProto(testKey0), 2616 Properties: map[string]*pb.Value{ 2617 "Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ 2618 Values: []*pb.Value{ 2619 {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, 2620 {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, 2621 }, 2622 }, 2623 }}, 2624 "Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ 2625 Values: []*pb.Value{ 2626 {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, 2627 {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, 2628 }, 2629 }, 2630 }}, 2631 "Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ 2632 Values: []*pb.Value{ 2633 {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2634 {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2635 }, 2636 }, 2637 }}, 2638 }, 2639 }, 2640 wantLoad: &GrandparentOfSliceFlatten{ 2641 Parent: ParentOfSlice{ 2642 Children: []Child{ 2643 { 2644 I: 1, 2645 Grandchild: Grandchild{ 2646 S: "grandchild loaded", 2647 }, 2648 }, 2649 { 2650 I: 1, 2651 Grandchild: Grandchild{ 2652 S: "grandchild loaded", 2653 }, 2654 }, 2655 }, 2656 Strings: []plsString{ 2657 "LOADED", 2658 "LOADED", 2659 }, 2660 }, 2661 }, 2662 }, 2663 { 2664 desc: "parent has flatten option, children (slice of ptrs) impl PLS", 2665 src: &GrandparentOfSlicePtrsFlatten{ 2666 Parent: ParentOfSlicePtrs{ 2667 Children: []*Child{ 2668 { 2669 I: 7, 2670 Grandchild: Grandchild{ 2671 S: "BAD", 2672 }, 2673 }, 2674 { 2675 I: 9, 2676 Grandchild: Grandchild{ 2677 S: "BAD2", 2678 }, 2679 }, 2680 }, 2681 Strings: []*plsString{ 2682 ptrToplsString("something1"), 2683 ptrToplsString("something1"), 2684 }, 2685 }, 2686 }, 2687 wantSave: &pb.Entity{ 2688 Key: keyToProto(testKey0), 2689 Properties: map[string]*pb.Value{ 2690 "Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ 2691 Values: []*pb.Value{ 2692 {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, 2693 {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, 2694 }, 2695 }, 2696 }}, 2697 "Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ 2698 Values: []*pb.Value{ 2699 {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, 2700 {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, 2701 }, 2702 }, 2703 }}, 2704 "Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ 2705 Values: []*pb.Value{ 2706 {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2707 {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, 2708 }, 2709 }, 2710 }}, 2711 }, 2712 }, 2713 wantLoad: &GrandparentOfSlicePtrsFlatten{ 2714 Parent: ParentOfSlicePtrs{ 2715 Children: []*Child{ 2716 { 2717 I: 1, 2718 Grandchild: Grandchild{ 2719 S: "grandchild loaded", 2720 }, 2721 }, 2722 { 2723 I: 1, 2724 Grandchild: Grandchild{ 2725 S: "grandchild loaded", 2726 }, 2727 }, 2728 }, 2729 Strings: []*plsString{ 2730 ptrToplsString("LOADED"), 2731 ptrToplsString("LOADED"), 2732 }, 2733 }, 2734 }, 2735 }, 2736 } 2737 2738 for _, tc := range testCases { 2739 e, err := saveEntity(testKey0, tc.src) 2740 if tc.saveErr == "" { // Want no error. 2741 if err != nil { 2742 t.Errorf("%s: save: %v", tc.desc, err) 2743 continue 2744 } 2745 if !testutil.Equal(e, tc.wantSave) { 2746 t.Errorf("%s: save: \ngot: %+v\nwant: %+v", tc.desc, e, tc.wantSave) 2747 continue 2748 } 2749 } else { // Want error. 2750 if err == nil { 2751 t.Errorf("%s: save: want err", tc.desc) 2752 continue 2753 } 2754 if !strings.Contains(err.Error(), tc.saveErr) { 2755 t.Errorf("%s: save: \ngot err '%s'\nwant err '%s'", tc.desc, err.Error(), tc.saveErr) 2756 } 2757 continue 2758 } 2759 2760 gota := reflect.New(reflect.TypeOf(tc.wantLoad).Elem()).Interface() 2761 err = loadEntityProto(gota, e) 2762 if tc.loadErr == "" { // Want no error. 2763 if err != nil { 2764 t.Errorf("%s: load: %v", tc.desc, err) 2765 continue 2766 } 2767 if !testutil.Equal(gota, tc.wantLoad) { 2768 t.Errorf("%s: load: \ngot: %+v\nwant: %+v", tc.desc, gota, tc.wantLoad) 2769 continue 2770 } 2771 } else { // Want error. 2772 if err == nil { 2773 t.Errorf("%s: load: want err", tc.desc) 2774 continue 2775 } 2776 if !strings.Contains(err.Error(), tc.loadErr) { 2777 t.Errorf("%s: load: \ngot err '%s'\nwant err '%s'", tc.desc, err.Error(), tc.loadErr) 2778 } 2779 } 2780 } 2781} 2782 2783func TestQueryConstruction(t *testing.T) { 2784 tests := []struct { 2785 q, exp *Query 2786 err string 2787 }{ 2788 { 2789 q: NewQuery("Foo"), 2790 exp: &Query{ 2791 kind: "Foo", 2792 limit: -1, 2793 }, 2794 }, 2795 { 2796 // Regular filtered query with standard spacing. 2797 q: NewQuery("Foo").Filter("foo >", 7), 2798 exp: &Query{ 2799 kind: "Foo", 2800 filter: []filter{ 2801 { 2802 FieldName: "foo", 2803 Op: greaterThan, 2804 Value: 7, 2805 }, 2806 }, 2807 limit: -1, 2808 }, 2809 }, 2810 { 2811 // Filtered query with no spacing. 2812 q: NewQuery("Foo").Filter("foo=", 6), 2813 exp: &Query{ 2814 kind: "Foo", 2815 filter: []filter{ 2816 { 2817 FieldName: "foo", 2818 Op: equal, 2819 Value: 6, 2820 }, 2821 }, 2822 limit: -1, 2823 }, 2824 }, 2825 { 2826 // Filtered query with funky spacing. 2827 q: NewQuery("Foo").Filter(" foo< ", 8), 2828 exp: &Query{ 2829 kind: "Foo", 2830 filter: []filter{ 2831 { 2832 FieldName: "foo", 2833 Op: lessThan, 2834 Value: 8, 2835 }, 2836 }, 2837 limit: -1, 2838 }, 2839 }, 2840 { 2841 // Filtered query with multicharacter op. 2842 q: NewQuery("Foo").Filter("foo >=", 9), 2843 exp: &Query{ 2844 kind: "Foo", 2845 filter: []filter{ 2846 { 2847 FieldName: "foo", 2848 Op: greaterEq, 2849 Value: 9, 2850 }, 2851 }, 2852 limit: -1, 2853 }, 2854 }, 2855 { 2856 // Query with ordering. 2857 q: NewQuery("Foo").Order("bar"), 2858 exp: &Query{ 2859 kind: "Foo", 2860 order: []order{ 2861 { 2862 FieldName: "bar", 2863 Direction: ascending, 2864 }, 2865 }, 2866 limit: -1, 2867 }, 2868 }, 2869 { 2870 // Query with reverse ordering, and funky spacing. 2871 q: NewQuery("Foo").Order(" - bar"), 2872 exp: &Query{ 2873 kind: "Foo", 2874 order: []order{ 2875 { 2876 FieldName: "bar", 2877 Direction: descending, 2878 }, 2879 }, 2880 limit: -1, 2881 }, 2882 }, 2883 { 2884 // Query with an empty ordering. 2885 q: NewQuery("Foo").Order(""), 2886 err: "empty order", 2887 }, 2888 { 2889 // Query with a + ordering. 2890 q: NewQuery("Foo").Order("+bar"), 2891 err: "invalid order", 2892 }, 2893 } 2894 for i, test := range tests { 2895 if test.q.err != nil { 2896 got := test.q.err.Error() 2897 if !strings.Contains(got, test.err) { 2898 t.Errorf("%d: error mismatch: got %q want something containing %q", i, got, test.err) 2899 } 2900 continue 2901 } 2902 if !testutil.Equal(test.q, test.exp, cmp.AllowUnexported(Query{})) { 2903 t.Errorf("%d: mismatch: got %v want %v", i, test.q, test.exp) 2904 } 2905 } 2906} 2907 2908func TestPutMultiTypes(t *testing.T) { 2909 ctx := context.Background() 2910 type S struct { 2911 A int 2912 B string 2913 } 2914 2915 testCases := []struct { 2916 desc string 2917 src interface{} 2918 wantErr bool 2919 }{ 2920 // Test cases to check each of the valid input types for src. 2921 // Each case has the same elements. 2922 { 2923 desc: "type []struct", 2924 src: []S{ 2925 {1, "one"}, {2, "two"}, 2926 }, 2927 }, 2928 { 2929 desc: "type []*struct", 2930 src: []*S{ 2931 {1, "one"}, {2, "two"}, 2932 }, 2933 }, 2934 { 2935 desc: "type []interface{} with PLS elems", 2936 src: []interface{}{ 2937 &PropertyList{Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}}, 2938 &PropertyList{Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}}, 2939 }, 2940 }, 2941 { 2942 desc: "type []interface{} with struct ptr elems", 2943 src: []interface{}{ 2944 &S{1, "one"}, &S{2, "two"}, 2945 }, 2946 }, 2947 { 2948 desc: "type []PropertyLoadSaver{}", 2949 src: []PropertyLoadSaver{ 2950 &PropertyList{Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}}, 2951 &PropertyList{Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}}, 2952 }, 2953 }, 2954 { 2955 desc: "type []P (non-pointer, *P implements PropertyLoadSaver)", 2956 src: []PropertyList{ 2957 {Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}}, 2958 {Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}}, 2959 }, 2960 }, 2961 // Test some invalid cases. 2962 { 2963 desc: "type []interface{} with struct elems", 2964 src: []interface{}{ 2965 S{1, "one"}, S{2, "two"}, 2966 }, 2967 wantErr: true, 2968 }, 2969 { 2970 desc: "PropertyList", 2971 src: PropertyList{ 2972 Property{Name: "A", Value: 1}, 2973 Property{Name: "B", Value: "one"}, 2974 }, 2975 wantErr: true, 2976 }, 2977 { 2978 desc: "type []int", 2979 src: []int{1, 2}, 2980 wantErr: true, 2981 }, 2982 { 2983 desc: "not a slice", 2984 src: S{1, "one"}, 2985 wantErr: true, 2986 }, 2987 } 2988 2989 // Use the same keys and expected entities for all tests. 2990 keys := []*Key{ 2991 NameKey("testKind", "first", nil), 2992 NameKey("testKind", "second", nil), 2993 } 2994 want := []*pb.Mutation{ 2995 {Operation: &pb.Mutation_Upsert{ 2996 Upsert: &pb.Entity{ 2997 Key: keyToProto(keys[0]), 2998 Properties: map[string]*pb.Value{ 2999 "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, 3000 "B": {ValueType: &pb.Value_StringValue{StringValue: "one"}}, 3001 }, 3002 }}}, 3003 {Operation: &pb.Mutation_Upsert{ 3004 Upsert: &pb.Entity{ 3005 Key: keyToProto(keys[1]), 3006 Properties: map[string]*pb.Value{ 3007 "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, 3008 "B": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, 3009 }, 3010 }}}, 3011 } 3012 3013 for _, tt := range testCases { 3014 // Set up a fake client which captures upserts. 3015 var got []*pb.Mutation 3016 client := &Client{ 3017 client: &fakeClient{ 3018 commitFn: func(req *pb.CommitRequest) (*pb.CommitResponse, error) { 3019 got = req.Mutations 3020 return &pb.CommitResponse{}, nil 3021 }, 3022 }, 3023 } 3024 3025 _, err := client.PutMulti(ctx, keys, tt.src) 3026 if err != nil { 3027 if !tt.wantErr { 3028 t.Errorf("%s: error %v", tt.desc, err) 3029 } 3030 continue 3031 } 3032 if tt.wantErr { 3033 t.Errorf("%s: wanted error, but none returned", tt.desc) 3034 continue 3035 } 3036 if len(got) != len(want) { 3037 t.Errorf("%s: got %d entities, want %d", tt.desc, len(got), len(want)) 3038 continue 3039 } 3040 for i, e := range got { 3041 if !proto.Equal(e, want[i]) { 3042 t.Logf("%s: entity %d doesn't match\ngot: %v\nwant: %v", tt.desc, i, e, want[i]) 3043 } 3044 } 3045 } 3046} 3047 3048func TestNoIndexOnSliceProperties(t *testing.T) { 3049 // Check that ExcludeFromIndexes is set on the inner elements, 3050 // rather than the top-level ArrayValue value. 3051 pl := PropertyList{ 3052 Property{ 3053 Name: "repeated", 3054 Value: []interface{}{ 3055 123, 3056 false, 3057 "short", 3058 strings.Repeat("a", 1503), 3059 }, 3060 NoIndex: true, 3061 }, 3062 } 3063 key := NameKey("dummy", "dummy", nil) 3064 3065 entity, err := saveEntity(key, &pl) 3066 if err != nil { 3067 t.Fatalf("saveEntity: %v", err) 3068 } 3069 3070 want := &pb.Value{ 3071 ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ 3072 {ValueType: &pb.Value_IntegerValue{IntegerValue: 123}, ExcludeFromIndexes: true}, 3073 {ValueType: &pb.Value_BooleanValue{BooleanValue: false}, ExcludeFromIndexes: true}, 3074 {ValueType: &pb.Value_StringValue{StringValue: "short"}, ExcludeFromIndexes: true}, 3075 {ValueType: &pb.Value_StringValue{StringValue: strings.Repeat("a", 1503)}, ExcludeFromIndexes: true}, 3076 }}}, 3077 } 3078 if got := entity.Properties["repeated"]; !proto.Equal(got, want) { 3079 t.Errorf("Entity proto differs\ngot: %v\nwant: %v", got, want) 3080 } 3081} 3082 3083type byName PropertyList 3084 3085func (s byName) Len() int { return len(s) } 3086func (s byName) Less(i, j int) bool { return s[i].Name < s[j].Name } 3087func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 3088 3089// sortPL sorts the property list by property name, and 3090// recursively sorts any nested property lists, or nested slices of 3091// property lists. 3092func sortPL(pl PropertyList) { 3093 sort.Stable(byName(pl)) 3094 for _, p := range pl { 3095 switch p.Value.(type) { 3096 case *Entity: 3097 sortPL(p.Value.(*Entity).Properties) 3098 case []interface{}: 3099 for _, p2 := range p.Value.([]interface{}) { 3100 if nent, ok := p2.(*Entity); ok { 3101 sortPL(nent.Properties) 3102 } 3103 } 3104 } 3105 } 3106} 3107 3108func TestValidGeoPoint(t *testing.T) { 3109 testCases := []struct { 3110 desc string 3111 pt GeoPoint 3112 want bool 3113 }{ 3114 { 3115 "valid", 3116 GeoPoint{67.21, 13.37}, 3117 true, 3118 }, 3119 { 3120 "high lat", 3121 GeoPoint{-90.01, 13.37}, 3122 false, 3123 }, 3124 { 3125 "low lat", 3126 GeoPoint{90.01, 13.37}, 3127 false, 3128 }, 3129 { 3130 "high lng", 3131 GeoPoint{67.21, 182}, 3132 false, 3133 }, 3134 { 3135 "low lng", 3136 GeoPoint{67.21, -181}, 3137 false, 3138 }, 3139 } 3140 3141 for _, tc := range testCases { 3142 if got := tc.pt.Valid(); got != tc.want { 3143 t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want) 3144 } 3145 } 3146} 3147 3148func TestPutInvalidEntity(t *testing.T) { 3149 // Test that trying to put an invalid entity always returns the correct error 3150 // type. 3151 3152 // Fake client that can pretend to start a transaction. 3153 fakeClient := &fakeDatastoreClient{ 3154 beginTransaction: func(*pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) { 3155 return &pb.BeginTransactionResponse{ 3156 Transaction: []byte("deadbeef"), 3157 }, nil 3158 }, 3159 } 3160 client := &Client{ 3161 client: fakeClient, 3162 } 3163 3164 ctx := context.Background() 3165 key := IncompleteKey("kind", nil) 3166 3167 _, err := client.Put(ctx, key, "invalid entity") 3168 if err != ErrInvalidEntityType { 3169 t.Errorf("client.Put returned err %v, want %v", err, ErrInvalidEntityType) 3170 } 3171 3172 _, err = client.PutMulti(ctx, []*Key{key}, []interface{}{"invalid entity"}) 3173 if me, ok := err.(MultiError); !ok { 3174 t.Errorf("client.PutMulti returned err %v, want MultiError type", err) 3175 } else if len(me) != 1 || me[0] != ErrInvalidEntityType { 3176 t.Errorf("client.PutMulti returned err %v, want MulitError{ErrInvalidEntityType}", err) 3177 } 3178 3179 client.RunInTransaction(ctx, func(tx *Transaction) error { 3180 _, err := tx.Put(key, "invalid entity") 3181 if err != ErrInvalidEntityType { 3182 t.Errorf("tx.Put returned err %v, want %v", err, ErrInvalidEntityType) 3183 } 3184 3185 _, err = tx.PutMulti([]*Key{key}, []interface{}{"invalid entity"}) 3186 if me, ok := err.(MultiError); !ok { 3187 t.Errorf("tx.PutMulti returned err %v, want MultiError type", err) 3188 } else if len(me) != 1 || me[0] != ErrInvalidEntityType { 3189 t.Errorf("tx.PutMulti returned err %v, want MulitError{ErrInvalidEntityType}", err) 3190 } 3191 3192 return errors.New("bang") // Return error: we don't actually want to commit. 3193 }) 3194} 3195 3196func TestDeferred(t *testing.T) { 3197 type Ent struct { 3198 A int 3199 B string 3200 } 3201 3202 keys := []*Key{ 3203 NameKey("testKind", "first", nil), 3204 NameKey("testKind", "second", nil), 3205 } 3206 3207 entity1 := &pb.Entity{ 3208 Key: keyToProto(keys[0]), 3209 Properties: map[string]*pb.Value{ 3210 "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, 3211 "B": {ValueType: &pb.Value_StringValue{StringValue: "one"}}, 3212 }, 3213 } 3214 entity2 := &pb.Entity{ 3215 Key: keyToProto(keys[1]), 3216 Properties: map[string]*pb.Value{ 3217 "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, 3218 "B": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, 3219 }, 3220 } 3221 3222 // count keeps track of the number of times fakeClient.lookup has been 3223 // called. 3224 var count int 3225 // Fake client that will return Deferred keys in resp on the first call. 3226 fakeClient := &fakeDatastoreClient{ 3227 lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) { 3228 count++ 3229 // On the first call, we return deferred keys. 3230 if count == 1 { 3231 return &pb.LookupResponse{ 3232 Found: []*pb.EntityResult{ 3233 { 3234 Entity: entity1, 3235 Version: 1, 3236 }, 3237 }, 3238 Deferred: []*pb.Key{ 3239 keyToProto(keys[1]), 3240 }, 3241 }, nil 3242 } 3243 3244 // On the second call, we do not return any more deferred keys. 3245 return &pb.LookupResponse{ 3246 Found: []*pb.EntityResult{ 3247 { 3248 Entity: entity2, 3249 Version: 1, 3250 }, 3251 }, 3252 }, nil 3253 }, 3254 } 3255 client := &Client{ 3256 client: fakeClient, 3257 } 3258 3259 ctx := context.Background() 3260 3261 dst := make([]Ent, len(keys)) 3262 err := client.GetMulti(ctx, keys, dst) 3263 if err != nil { 3264 t.Fatalf("client.Get: %v", err) 3265 } 3266 3267 if count != 2 { 3268 t.Fatalf("expected client.lookup to be called 2 times. Got %d", count) 3269 } 3270 3271 if len(dst) != 2 { 3272 t.Fatalf("expected 2 entities returned, got %d", len(dst)) 3273 } 3274 3275 for _, e := range dst { 3276 if e.A == 1 { 3277 if e.B != "one" { 3278 t.Fatalf("unexpected entity %+v", e) 3279 } 3280 } else if e.A == 2 { 3281 if e.B != "two" { 3282 t.Fatalf("unexpected entity %+v", e) 3283 } 3284 } else { 3285 t.Fatalf("unexpected entity %+v", e) 3286 } 3287 } 3288 3289} 3290 3291type KeyLoaderEnt struct { 3292 A int 3293 K *Key 3294} 3295 3296func (e *KeyLoaderEnt) Load(p []Property) error { 3297 e.A = 2 3298 return nil 3299} 3300 3301func (e *KeyLoaderEnt) LoadKey(k *Key) error { 3302 e.K = k 3303 return nil 3304} 3305 3306func (e *KeyLoaderEnt) Save() ([]Property, error) { 3307 return []Property{{Name: "A", Value: int64(3)}}, nil 3308} 3309 3310func TestKeyLoaderEndToEnd(t *testing.T) { 3311 keys := []*Key{ 3312 NameKey("testKind", "first", nil), 3313 NameKey("testKind", "second", nil), 3314 } 3315 3316 entity1 := &pb.Entity{ 3317 Key: keyToProto(keys[0]), 3318 Properties: map[string]*pb.Value{ 3319 "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, 3320 "B": {ValueType: &pb.Value_StringValue{StringValue: "one"}}, 3321 }, 3322 } 3323 entity2 := &pb.Entity{ 3324 Key: keyToProto(keys[1]), 3325 Properties: map[string]*pb.Value{ 3326 "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, 3327 "B": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, 3328 }, 3329 } 3330 3331 fakeClient := &fakeDatastoreClient{ 3332 lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) { 3333 return &pb.LookupResponse{ 3334 Found: []*pb.EntityResult{ 3335 { 3336 Entity: entity1, 3337 Version: 1, 3338 }, 3339 { 3340 Entity: entity2, 3341 Version: 1, 3342 }, 3343 }, 3344 }, nil 3345 }, 3346 } 3347 client := &Client{ 3348 client: fakeClient, 3349 } 3350 3351 ctx := context.Background() 3352 3353 dst := make([]*KeyLoaderEnt, len(keys)) 3354 err := client.GetMulti(ctx, keys, dst) 3355 if err != nil { 3356 t.Fatalf("client.Get: %v", err) 3357 } 3358 3359 for i := range dst { 3360 if !testutil.Equal(dst[i].K, keys[i]) { 3361 t.Fatalf("unexpected entity %d to have key %+v, got %+v", i, keys[i], dst[i].K) 3362 } 3363 } 3364} 3365 3366func TestDeferredMissing(t *testing.T) { 3367 type Ent struct { 3368 A int 3369 B string 3370 } 3371 3372 keys := []*Key{ 3373 NameKey("testKind", "first", nil), 3374 NameKey("testKind", "second", nil), 3375 } 3376 3377 entity1 := &pb.Entity{ 3378 Key: keyToProto(keys[0]), 3379 } 3380 entity2 := &pb.Entity{ 3381 Key: keyToProto(keys[1]), 3382 } 3383 3384 var count int 3385 fakeClient := &fakeDatastoreClient{ 3386 lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) { 3387 count++ 3388 3389 if count == 1 { 3390 return &pb.LookupResponse{ 3391 Missing: []*pb.EntityResult{ 3392 { 3393 Entity: entity1, 3394 Version: 1, 3395 }, 3396 }, 3397 Deferred: []*pb.Key{ 3398 keyToProto(keys[1]), 3399 }, 3400 }, nil 3401 } 3402 3403 return &pb.LookupResponse{ 3404 Missing: []*pb.EntityResult{ 3405 { 3406 Entity: entity2, 3407 Version: 1, 3408 }, 3409 }, 3410 }, nil 3411 }, 3412 } 3413 client := &Client{ 3414 client: fakeClient, 3415 } 3416 3417 ctx := context.Background() 3418 3419 dst := make([]Ent, len(keys)) 3420 err := client.GetMulti(ctx, keys, dst) 3421 errs, ok := err.(MultiError) 3422 if !ok { 3423 t.Fatalf("expected error returns to be MultiError; got %v", err) 3424 } 3425 if len(errs) != 2 { 3426 t.Fatalf("expected 2 errors returns, got %d", len(errs)) 3427 } 3428 if errs[0] != ErrNoSuchEntity { 3429 t.Fatalf("expected error to be ErrNoSuchEntity; got %v", errs[0]) 3430 } 3431 if errs[1] != ErrNoSuchEntity { 3432 t.Fatalf("expected error to be ErrNoSuchEntity; got %v", errs[1]) 3433 } 3434 3435 if count != 2 { 3436 t.Fatalf("expected client.lookup to be called 2 times. Got %d", count) 3437 } 3438 3439 if len(dst) != 2 { 3440 t.Fatalf("expected 2 entities returned, got %d", len(dst)) 3441 } 3442 3443 for _, e := range dst { 3444 if e.A != 0 || e.B != "" { 3445 t.Fatalf("unexpected entity %+v", e) 3446 } 3447 } 3448} 3449 3450func TestGetWithNilKey(t *testing.T) { 3451 client := &Client{} 3452 err := client.Get(context.Background(), nil, []Property{}) 3453 if err != ErrInvalidKey { 3454 t.Fatalf("want ErrInvalidKey, got %v", err) 3455 } 3456} 3457 3458func TestGetMultiWithNilKey(t *testing.T) { 3459 client := &Client{} 3460 dest := make([]PropertyList, 1) 3461 err := client.GetMulti(context.Background(), []*Key{nil}, dest) 3462 if me, ok := err.(MultiError); !ok { 3463 t.Fatalf("want MultiError, got %v", err) 3464 } else if len(me) != 1 || me[0] != ErrInvalidKey { 3465 t.Fatalf("want MultiError{ErrInvalidKey}, got %v", me) 3466 } 3467} 3468 3469func TestGetWithIncompleteKey(t *testing.T) { 3470 client := &Client{} 3471 err := client.Get(context.Background(), &Key{Kind: "testKind"}, []Property{}) 3472 if err == nil { 3473 t.Fatalf("want err, got nil") 3474 } 3475} 3476 3477func TestGetMultiWithIncompleteKey(t *testing.T) { 3478 client := &Client{} 3479 dest := make([]PropertyList, 1) 3480 err := client.GetMulti(context.Background(), []*Key{{Kind: "testKind"}}, dest) 3481 if me, ok := err.(MultiError); !ok { 3482 t.Fatalf("want MultiError, got %v", err) 3483 } else if len(me) != 1 || me[0] == nil { 3484 t.Fatalf("want MultiError{err}, got %v", me) 3485 } 3486} 3487 3488func TestDeleteWithNilKey(t *testing.T) { 3489 client := &Client{} 3490 err := client.Delete(context.Background(), nil) 3491 if err != ErrInvalidKey { 3492 t.Fatalf("want ErrInvalidKey, got %v", err) 3493 } 3494} 3495 3496func TestDeleteMultiWithNilKey(t *testing.T) { 3497 client := &Client{} 3498 err := client.DeleteMulti(context.Background(), []*Key{nil}) 3499 if me, ok := err.(MultiError); !ok { 3500 t.Fatalf("want MultiError, got %v", err) 3501 } else if len(me) != 1 || me[0] != ErrInvalidKey { 3502 t.Fatalf("want MultiError{ErrInvalidKey}, got %v", me) 3503 } 3504} 3505 3506func TestDeleteWithIncompleteKey(t *testing.T) { 3507 client := &Client{} 3508 err := client.Delete(context.Background(), &Key{Kind: "testKind"}) 3509 if err == nil { 3510 t.Fatalf("want err, got nil") 3511 } 3512} 3513 3514func TestDeleteMultiWithIncompleteKey(t *testing.T) { 3515 client := &Client{} 3516 err := client.DeleteMulti(context.Background(), []*Key{{Kind: "testKind"}}) 3517 if me, ok := err.(MultiError); !ok { 3518 t.Fatalf("want MultiError, got %v", err) 3519 } else if len(me) != 1 || me[0] == nil { 3520 t.Fatalf("want MultiError{err}, got %v", me) 3521 } 3522} 3523 3524type fakeDatastoreClient struct { 3525 pb.DatastoreClient 3526 3527 // Optional handlers for the datastore methods. 3528 // Any handlers left undefined will return an error. 3529 lookup func(*pb.LookupRequest) (*pb.LookupResponse, error) 3530 runQuery func(*pb.RunQueryRequest) (*pb.RunQueryResponse, error) 3531 beginTransaction func(*pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) 3532 commit func(*pb.CommitRequest) (*pb.CommitResponse, error) 3533 rollback func(*pb.RollbackRequest) (*pb.RollbackResponse, error) 3534 allocateIds func(*pb.AllocateIdsRequest) (*pb.AllocateIdsResponse, error) 3535} 3536 3537func (c *fakeDatastoreClient) Lookup(ctx context.Context, in *pb.LookupRequest, opts ...grpc.CallOption) (*pb.LookupResponse, error) { 3538 if c.lookup == nil { 3539 return nil, errors.New("no lookup handler defined") 3540 } 3541 return c.lookup(in) 3542} 3543func (c *fakeDatastoreClient) RunQuery(ctx context.Context, in *pb.RunQueryRequest, opts ...grpc.CallOption) (*pb.RunQueryResponse, error) { 3544 if c.runQuery == nil { 3545 return nil, errors.New("no runQuery handler defined") 3546 } 3547 return c.runQuery(in) 3548} 3549func (c *fakeDatastoreClient) BeginTransaction(ctx context.Context, in *pb.BeginTransactionRequest, opts ...grpc.CallOption) (*pb.BeginTransactionResponse, error) { 3550 if c.beginTransaction == nil { 3551 return nil, errors.New("no beginTransaction handler defined") 3552 } 3553 return c.beginTransaction(in) 3554} 3555func (c *fakeDatastoreClient) Commit(ctx context.Context, in *pb.CommitRequest, opts ...grpc.CallOption) (*pb.CommitResponse, error) { 3556 if c.commit == nil { 3557 return nil, errors.New("no commit handler defined") 3558 } 3559 return c.commit(in) 3560} 3561func (c *fakeDatastoreClient) Rollback(ctx context.Context, in *pb.RollbackRequest, opts ...grpc.CallOption) (*pb.RollbackResponse, error) { 3562 if c.rollback == nil { 3563 return nil, errors.New("no rollback handler defined") 3564 } 3565 return c.rollback(in) 3566} 3567func (c *fakeDatastoreClient) AllocateIds(ctx context.Context, in *pb.AllocateIdsRequest, opts ...grpc.CallOption) (*pb.AllocateIdsResponse, error) { 3568 if c.allocateIds == nil { 3569 return nil, errors.New("no allocateIds handler defined") 3570 } 3571 return c.allocateIds(in) 3572} 3573