go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/impl/memory/datastore_test.go (about) 1 // Copyright 2015 The LUCI Authors. 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 15 package memory 16 17 import ( 18 "context" 19 "encoding/hex" 20 "errors" 21 "fmt" 22 "sync" 23 "sync/atomic" 24 "testing" 25 "time" 26 27 ds "go.chromium.org/luci/gae/service/datastore" 28 infoS "go.chromium.org/luci/gae/service/info" 29 30 . "github.com/smartystreets/goconvey/convey" 31 . "go.chromium.org/luci/common/testing/assertions" 32 ) 33 34 type MetaGroup struct { 35 _id int64 `gae:"$id,1"` 36 _kind string `gae:"$kind,__entity_group__"` 37 Parent *ds.Key `gae:"$parent"` 38 39 Version int64 `gae:"__version__"` 40 } 41 42 func testGetMeta(c context.Context, k *ds.Key) int64 { 43 mg := &MetaGroup{Parent: k.Root()} 44 if err := ds.Get(c, mg); err != nil { 45 panic(err) 46 } 47 return mg.Version 48 } 49 50 type Nested struct { 51 Inner int 52 } 53 54 type Foo struct { 55 ID int64 `gae:"$id"` 56 Parent *ds.Key `gae:"$parent"` 57 58 Val int 59 Name string 60 Multi []string 61 Key *ds.Key 62 Nested Nested `gae:",lsp"` 63 64 Scatter []byte `gae:"__scatter__"` // this is normally invisible 65 } 66 67 func TestDatastoreSingleReadWriter(t *testing.T) { 68 t.Parallel() 69 70 Convey("Datastore single reads and writes", t, func() { 71 c := Use(context.Background()) 72 So(ds.Raw(c), ShouldNotBeNil) 73 74 Convey("getting objects that DNE is an error", func() { 75 So(ds.Get(c, &Foo{ID: 1}), ShouldEqual, ds.ErrNoSuchEntity) 76 }) 77 78 Convey("bad namespaces fail", func() { 79 _, err := infoS.Namespace(c, "$$blzyall") 80 So(err.Error(), ShouldContainSubstring, "namespace \"$$blzyall\" does not match") 81 }) 82 83 Convey("Can Put stuff", func() { 84 // with an incomplete key! 85 f := &Foo{ 86 Val: 10, 87 Multi: []string{"foo", "bar"}, 88 Key: ds.MakeKey(c, "Bar", "Baz"), 89 Nested: Nested{ 90 Inner: 456, 91 }, 92 } 93 So(ds.Put(c, f), ShouldBeNil) 94 k := ds.KeyForObj(c, f) 95 So(k.String(), ShouldEqual, "dev~app::/Foo,1") 96 97 Convey("and Get it back", func() { 98 newFoo := &Foo{ID: 1} 99 So(ds.Get(c, newFoo), ShouldBeNil) 100 So(newFoo, ShouldResemble, f) 101 102 Convey("but it's hidden from a different namespace", func() { 103 c, err := infoS.Namespace(c, "whombat") 104 So(err, ShouldBeNil) 105 So(ds.Get(c, f), ShouldEqual, ds.ErrNoSuchEntity) 106 }) 107 108 Convey("and we can Delete it", func() { 109 So(ds.Delete(c, k), ShouldBeNil) 110 So(ds.Get(c, newFoo), ShouldEqual, ds.ErrNoSuchEntity) 111 }) 112 113 }) 114 Convey("Can Get it back as a PropertyMap", func() { 115 pmap := ds.PropertyMap{ 116 "$id": propNI(1), 117 "$kind": propNI("Foo"), 118 } 119 So(ds.Get(c, pmap), ShouldBeNil) 120 So(pmap, ShouldResemble, ds.PropertyMap{ 121 "$id": propNI(1), 122 "$kind": propNI("Foo"), 123 "Name": prop(""), 124 "Val": prop(10), 125 "Multi": ds.PropertySlice{prop("foo"), prop("bar")}, 126 "Key": prop(ds.MkKeyContext("dev~app", "").MakeKey("Bar", "Baz")), 127 "Nested": prop(ds.PropertyMap{ 128 "Inner": prop(456), 129 }), 130 }) 131 }) 132 Convey("Deleting with a bogus key is bad", func() { 133 So(ds.IsErrInvalidKey(ds.Delete(c, ds.NewKey(c, "Foo", "wat", 100, nil))), ShouldBeTrue) 134 }) 135 Convey("Deleting a DNE entity is fine", func() { 136 So(ds.Delete(c, ds.NewKey(c, "Foo", "wat", 0, nil)), ShouldBeNil) 137 }) 138 139 Convey("Deleting entities from a nonexistant namespace works", func(gctx C) { 140 c := infoS.MustNamespace(c, "noexist") 141 keys := make([]*ds.Key, 10) 142 for i := range keys { 143 keys[i] = ds.MakeKey(c, "Kind", i+1) 144 } 145 So(ds.Delete(c, keys), ShouldBeNil) 146 count := 0 147 So(ds.Raw(c).DeleteMulti(keys, func(idx int, err error) { 148 gctx.So(idx, ShouldEqual, count) 149 gctx.So(err, ShouldBeNil) 150 151 count++ 152 }), ShouldBeNil) 153 So(count, ShouldEqual, len(keys)) 154 }) 155 156 Convey("with multiple puts", func() { 157 So(testGetMeta(c, k), ShouldEqual, 1) 158 159 foos := make([]Foo, 10) 160 for i := range foos { 161 foos[i].Val = 10 162 foos[i].Parent = k 163 } 164 So(ds.Put(c, foos), ShouldBeNil) 165 So(testGetMeta(c, k), ShouldEqual, 11) 166 167 keys := make([]*ds.Key, len(foos)) 168 for i, f := range foos { 169 keys[i] = ds.KeyForObj(c, &f) 170 } 171 172 Convey("ensure that group versions persist across deletes", func() { 173 So(ds.Delete(c, append(keys, k)), ShouldBeNil) 174 175 ds.GetTestable(c).CatchupIndexes() 176 177 count := 0 178 So(ds.Run(c, ds.NewQuery(""), func(_ *ds.Key) { 179 count++ 180 }), ShouldBeNil) 181 So(count, ShouldEqual, 2) 182 183 So(testGetMeta(c, k), ShouldEqual, 22) 184 185 So(ds.Put(c, &Foo{ID: 1}), ShouldBeNil) 186 So(testGetMeta(c, k), ShouldEqual, 23) 187 }) 188 189 Convey("can Get", func() { 190 vals := make([]ds.PropertyMap, len(keys)) 191 for i := range vals { 192 vals[i] = ds.PropertyMap{} 193 So(vals[i].SetMeta("key", keys[i]), ShouldBeTrue) 194 } 195 So(ds.Get(c, vals), ShouldBeNil) 196 197 for i, val := range vals { 198 So(val, ShouldResemble, ds.PropertyMap{ 199 "Val": ds.MkProperty(10), 200 "Name": ds.MkProperty(""), 201 "$key": ds.MkPropertyNI(keys[i]), 202 "Key": ds.MkProperty(nil), 203 "Nested": ds.MkProperty(ds.PropertyMap{ 204 "Inner": ds.MkProperty(0), 205 }), 206 }) 207 } 208 }) 209 210 }) 211 212 Convey("allocating ids prevents their use", func() { 213 keys := ds.NewIncompleteKeys(c, 100, "Foo", nil) 214 So(ds.AllocateIDs(c, keys), ShouldBeNil) 215 So(len(keys), ShouldEqual, 100) 216 217 // Assert that none of our keys share the same ID. 218 ids := make(map[int64]struct{}) 219 for _, k := range keys { 220 ids[k.IntID()] = struct{}{} 221 } 222 So(len(ids), ShouldEqual, len(keys)) 223 224 // Put a new object and ensure that it is allocated an unused ID. 225 f := &Foo{Val: 10} 226 So(ds.Put(c, f), ShouldBeNil) 227 k := ds.KeyForObj(c, f) 228 So(k.String(), ShouldEqual, "dev~app::/Foo,102") 229 230 _, ok := ids[k.IntID()] 231 So(ok, ShouldBeFalse) 232 }) 233 }) 234 235 Convey("implements DSTransactioner", func() { 236 Convey("Put", func() { 237 f := &Foo{Val: 10} 238 So(ds.Put(c, f), ShouldBeNil) 239 k := ds.KeyForObj(c, f) 240 So(k.String(), ShouldEqual, "dev~app::/Foo,1") 241 242 Convey("can describe its transaction state", func() { 243 So(ds.CurrentTransaction(c), ShouldBeNil) 244 245 err := ds.RunInTransaction(c, func(c context.Context) error { 246 So(ds.CurrentTransaction(c), ShouldNotBeNil) 247 248 // Can reset to nil. 249 nc := ds.WithoutTransaction(c) 250 So(ds.CurrentTransaction(nc), ShouldBeNil) 251 return nil 252 }, nil) 253 So(err, ShouldBeNil) 254 }) 255 256 Convey("can Put new entity groups", func() { 257 err := ds.RunInTransaction(c, func(c context.Context) error { 258 f := &Foo{Val: 100} 259 So(ds.Put(c, f), ShouldBeNil) 260 So(f.ID, ShouldEqual, 2) 261 262 f.ID = 0 263 f.Val = 200 264 So(ds.Put(c, f), ShouldBeNil) 265 So(f.ID, ShouldEqual, 3) 266 267 return nil 268 }, nil) 269 So(err, ShouldBeNil) 270 271 f := &Foo{ID: 2} 272 So(ds.Get(c, f), ShouldBeNil) 273 So(f.Val, ShouldEqual, 100) 274 275 f.ID = 3 276 So(ds.Get(c, f), ShouldBeNil) 277 So(f.Val, ShouldEqual, 200) 278 }) 279 280 Convey("can Put new entities in a current group", func() { 281 err := ds.RunInTransaction(c, func(c context.Context) error { 282 f := &Foo{Val: 100, Parent: k} 283 So(ds.Put(c, f), ShouldBeNil) 284 So(ds.KeyForObj(c, f).String(), ShouldEqual, "dev~app::/Foo,1/Foo,1") 285 286 f.ID = 0 287 f.Val = 200 288 So(ds.Put(c, f), ShouldBeNil) 289 So(ds.KeyForObj(c, f).String(), ShouldEqual, "dev~app::/Foo,1/Foo,2") 290 291 return nil 292 }, nil) 293 So(err, ShouldBeNil) 294 295 f := &Foo{ID: 1, Parent: k} 296 So(ds.Get(c, f), ShouldBeNil) 297 So(f.Val, ShouldEqual, 100) 298 299 f.ID = 2 300 So(ds.Get(c, f), ShouldBeNil) 301 So(f.Val, ShouldEqual, 200) 302 }) 303 304 Convey("Deletes work too", func() { 305 err := ds.RunInTransaction(c, func(c context.Context) error { 306 return ds.Delete(c, k) 307 }, nil) 308 So(err, ShouldBeNil) 309 So(ds.Get(c, &Foo{ID: 1}), ShouldEqual, ds.ErrNoSuchEntity) 310 }) 311 312 Convey("Get takes a snapshot", func() { 313 err := ds.RunInTransaction(c, func(c context.Context) error { 314 So(ds.Get(c, f), ShouldBeNil) 315 So(f.Val, ShouldEqual, 10) 316 317 // Don't ever do this in a real program unless you want to guarantee 318 // a failed transaction :) 319 f.Val = 11 320 So(ds.Put(ds.WithoutTransaction(c), f), ShouldBeNil) 321 322 So(ds.Get(c, f), ShouldBeNil) 323 So(f.Val, ShouldEqual, 10) 324 325 return nil 326 }, nil) 327 So(err, ShouldBeNil) 328 329 f := &Foo{ID: 1} 330 So(ds.Get(c, f), ShouldBeNil) 331 So(f.Val, ShouldEqual, 11) 332 }) 333 334 Convey("and snapshots are consistent even after Puts", func() { 335 err := ds.RunInTransaction(c, func(c context.Context) error { 336 f := &Foo{ID: 1} 337 So(ds.Get(c, f), ShouldBeNil) 338 So(f.Val, ShouldEqual, 10) 339 340 // Don't ever do this in a real program unless you want to guarantee 341 // a failed transaction :) 342 f.Val = 11 343 So(ds.Put(ds.WithoutTransaction(c), f), ShouldBeNil) 344 345 So(ds.Get(c, f), ShouldBeNil) 346 So(f.Val, ShouldEqual, 10) 347 348 f.Val = 20 349 So(ds.Put(c, f), ShouldBeNil) 350 351 So(ds.Get(c, f), ShouldBeNil) 352 So(f.Val, ShouldEqual, 10) // still gets 10 353 354 return nil 355 }, &ds.TransactionOptions{Attempts: 1}) 356 So(err.Error(), ShouldContainSubstring, "concurrent") 357 358 f := &Foo{ID: 1} 359 So(ds.Get(c, f), ShouldBeNil) 360 So(f.Val, ShouldEqual, 11) 361 }) 362 363 Convey("Reusing a transaction context is bad news", func() { 364 var txnCtx context.Context 365 err := ds.RunInTransaction(c, func(c context.Context) error { 366 txnCtx = c 367 So(ds.Get(c, f), ShouldBeNil) 368 return nil 369 }, nil) 370 So(err, ShouldBeNil) 371 So(ds.Get(txnCtx, f).Error(), ShouldContainSubstring, "expired") 372 }) 373 374 Convey("Nested transactions are rejected", func() { 375 err := ds.RunInTransaction(c, func(c context.Context) error { 376 err := ds.RunInTransaction(c, func(c context.Context) error { 377 panic("noooo") 378 }, nil) 379 So(err.Error(), ShouldContainSubstring, "nested transactions") 380 return nil 381 }, nil) 382 So(err, ShouldBeNil) 383 }) 384 385 Convey("Transactions can be escaped.", func() { 386 testError := errors.New("test error") 387 noTxnPM := ds.PropertyMap{ 388 "$kind": ds.MkProperty("Test"), 389 "$id": ds.MkProperty("no txn"), 390 } 391 392 err := ds.RunInTransaction(c, func(c context.Context) error { 393 So(ds.CurrentTransaction(c), ShouldNotBeNil) 394 395 pmap := ds.PropertyMap{ 396 "$kind": ds.MkProperty("Test"), 397 "$id": ds.MkProperty("quux"), 398 } 399 if err := ds.Put(c, pmap); err != nil { 400 return err 401 } 402 403 // Put an entity outside of the transaction so we can confirm that 404 // it was added even when the transaction fails. 405 if err := ds.Put(ds.WithoutTransaction(c), noTxnPM); err != nil { 406 return err 407 } 408 return testError 409 }, nil) 410 So(err, ShouldEqual, testError) 411 412 // Confirm that noTxnPM was added. 413 So(ds.CurrentTransaction(c), ShouldBeNil) 414 So(ds.Get(c, noTxnPM), ShouldBeNil) 415 }) 416 417 Convey("Concurrent transactions only accept one set of changes", func() { 418 // Note: I think this implementation is actually /slightly/ wrong. 419 // According to my read of the docs for appengine, when you open a 420 // transaction it actually (essentially) holds a reference to the 421 // entire datastore. Our implementation takes a snapshot of the 422 // entity group as soon as something observes/affects it. 423 // 424 // That said... I'm not sure if there's really a semantic difference. 425 err := ds.RunInTransaction(c, func(c context.Context) error { 426 So(ds.Put(c, &Foo{ID: 1, Val: 21}), ShouldBeNil) 427 428 err := ds.RunInTransaction(ds.WithoutTransaction(c), func(c context.Context) error { 429 So(ds.Put(c, &Foo{ID: 1, Val: 27}), ShouldBeNil) 430 return nil 431 }, nil) 432 So(err, ShouldBeNil) 433 434 return nil 435 }, nil) 436 So(err.Error(), ShouldContainSubstring, "concurrent") 437 438 f := &Foo{ID: 1} 439 So(ds.Get(c, f), ShouldBeNil) 440 So(f.Val, ShouldEqual, 27) 441 }) 442 443 Convey("Errors and panics", func() { 444 Convey("returning an error aborts", func() { 445 err := ds.RunInTransaction(c, func(c context.Context) error { 446 So(ds.Put(c, &Foo{ID: 1, Val: 200}), ShouldBeNil) 447 return fmt.Errorf("thingy") 448 }, nil) 449 So(err.Error(), ShouldEqual, "thingy") 450 451 f := &Foo{ID: 1} 452 So(ds.Get(c, f), ShouldBeNil) 453 So(f.Val, ShouldEqual, 10) 454 }) 455 456 Convey("panicing aborts", func() { 457 So(func() { 458 So(ds.RunInTransaction(c, func(c context.Context) error { 459 So(ds.Put(c, &Foo{Val: 200}), ShouldBeNil) 460 panic("wheeeeee") 461 }, nil), ShouldBeNil) 462 }, ShouldPanic) 463 464 f := &Foo{ID: 1} 465 So(ds.Get(c, f), ShouldBeNil) 466 So(f.Val, ShouldEqual, 10) 467 }) 468 }) 469 470 Convey("Transaction retries", func() { 471 tst := ds.GetTestable(c) 472 Reset(func() { tst.SetTransactionRetryCount(0) }) 473 474 Convey("SetTransactionRetryCount set to zero", func() { 475 tst.SetTransactionRetryCount(0) 476 calls := 0 477 So(ds.RunInTransaction(c, func(c context.Context) error { 478 calls++ 479 return nil 480 }, nil), ShouldBeNil) 481 So(calls, ShouldEqual, 1) 482 }) 483 484 Convey("default TransactionOptions is 3 attempts", func() { 485 tst.SetTransactionRetryCount(100) // more than 3 486 calls := 0 487 So(ds.RunInTransaction(c, func(c context.Context) error { 488 calls++ 489 return nil 490 }, nil), ShouldEqual, ds.ErrConcurrentTransaction) 491 So(calls, ShouldEqual, 3) 492 }) 493 494 Convey("non-default TransactionOptions ", func() { 495 tst.SetTransactionRetryCount(100) // more than 20 496 calls := 0 497 So(ds.RunInTransaction(c, func(c context.Context) error { 498 calls++ 499 return nil 500 }, &ds.TransactionOptions{Attempts: 20}), ShouldEqual, ds.ErrConcurrentTransaction) 501 So(calls, ShouldEqual, 20) 502 }) 503 504 Convey("SetTransactionRetryCount is respected", func() { 505 tst.SetTransactionRetryCount(1) // less than 3 506 calls := 0 507 So(ds.RunInTransaction(c, func(c context.Context) error { 508 calls++ 509 return nil 510 }, nil), ShouldBeNil) 511 So(calls, ShouldEqual, 2) 512 }) 513 514 Convey("fatal errors are not retried", func() { 515 tst.SetTransactionRetryCount(1) 516 calls := 0 517 So(ds.RunInTransaction(c, func(c context.Context) error { 518 calls++ 519 return fmt.Errorf("omg") 520 }, nil).Error(), ShouldEqual, "omg") 521 So(calls, ShouldEqual, 1) 522 }) 523 }) 524 525 Convey("Read-only transactions reject writes", func() { 526 So(ds.Put(c, &Foo{ID: 1, Val: 100}), ShouldBeNil) 527 var val int 528 529 So(ds.RunInTransaction(c, func(c context.Context) error { 530 foo := &Foo{ID: 1} 531 So(ds.Get(c, foo), ShouldBeNil) 532 val = foo.Val 533 534 foo.Val = 1337 535 return ds.Put(c, foo) 536 }, &ds.TransactionOptions{ReadOnly: true}), ShouldErrLike, "Attempting to write") 537 So(val, ShouldResemble, 100) 538 foo := &Foo{ID: 1} 539 So(ds.Get(c, foo), ShouldBeNil) 540 So(foo.Val, ShouldResemble, 100) 541 }) 542 543 Convey("Read-only transactions reject deletes", func() { 544 So(ds.Put(c, &Foo{ID: 1, Val: 100}), ShouldBeNil) 545 var val int 546 547 So(ds.RunInTransaction(c, func(c context.Context) error { 548 foo := &Foo{ID: 1} 549 So(ds.Get(c, foo), ShouldBeNil) 550 val = foo.Val 551 552 return ds.Delete(c, foo) 553 }, &ds.TransactionOptions{ReadOnly: true}), ShouldErrLike, "Attempting to delete") 554 So(val, ShouldResemble, 100) 555 So(ds.Get(c, &Foo{ID: 1}), ShouldBeNil) 556 }) 557 }) 558 }) 559 560 Convey("Testable.Consistent", func() { 561 Convey("false", func() { 562 ds.GetTestable(c).Consistent(false) // the default 563 for i := 0; i < 10; i++ { 564 So(ds.Put(c, &Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil) 565 } 566 q := ds.NewQuery("Foo").Gt("Val", 3) 567 count, err := ds.Count(c, q) 568 So(err, ShouldBeNil) 569 So(count, ShouldEqual, 0) 570 571 So(ds.Delete(c, ds.MakeKey(c, "Foo", 4)), ShouldBeNil) 572 573 count, err = ds.Count(c, q) 574 So(err, ShouldBeNil) 575 So(count, ShouldEqual, 0) 576 577 ds.GetTestable(c).Consistent(true) 578 count, err = ds.Count(c, q) 579 So(err, ShouldBeNil) 580 So(count, ShouldEqual, 6) 581 }) 582 583 Convey("true", func() { 584 ds.GetTestable(c).Consistent(true) 585 for i := 0; i < 10; i++ { 586 So(ds.Put(c, &Foo{ID: int64(i + 1), Val: i + 1}), ShouldBeNil) 587 } 588 q := ds.NewQuery("Foo").Gt("Val", 3) 589 count, err := ds.Count(c, q) 590 So(err, ShouldBeNil) 591 So(count, ShouldEqual, 7) 592 593 So(ds.Delete(c, ds.MakeKey(c, "Foo", 4)), ShouldBeNil) 594 595 count, err = ds.Count(c, q) 596 So(err, ShouldBeNil) 597 So(count, ShouldEqual, 6) 598 }) 599 }) 600 601 Convey("Testable.DisableSpecialEntities", func() { 602 ds.GetTestable(c).DisableSpecialEntities(true) 603 604 So(ds.Put(c, &Foo{}), ShouldErrLike, "allocateIDs is disabled") 605 606 So(ds.Put(c, &Foo{ID: 1}), ShouldBeNil) 607 608 ds.GetTestable(c).CatchupIndexes() 609 610 count, err := ds.Count(c, ds.NewQuery("")) 611 So(err, ShouldBeNil) 612 So(count, ShouldEqual, 1) // normally this would include __entity_group__ 613 }) 614 615 Convey("Datastore namespace interaction", func() { 616 run := func(rc context.Context, txn bool) (putErr, getErr, queryErr, countErr error) { 617 var foo Foo 618 619 putFunc := func(doC context.Context) error { 620 return ds.Put(doC, &foo) 621 } 622 623 doFunc := func(doC context.Context) { 624 getErr = ds.Get(doC, &foo) 625 626 q := ds.NewQuery("Foo").Ancestor(ds.KeyForObj(doC, &foo)) 627 queryErr = ds.Run(doC, q, func(f *Foo) error { return nil }) 628 _, countErr = ds.Count(doC, q) 629 } 630 631 if txn { 632 putErr = ds.RunInTransaction(rc, func(ic context.Context) error { 633 return putFunc(ic) 634 }, nil) 635 if putErr != nil { 636 return 637 } 638 639 ds.GetTestable(rc).CatchupIndexes() 640 ds.RunInTransaction(rc, func(ic context.Context) error { 641 doFunc(ic) 642 return nil 643 }, nil) 644 } else { 645 putErr = putFunc(rc) 646 if putErr != nil { 647 return 648 } 649 ds.GetTestable(rc).CatchupIndexes() 650 doFunc(rc) 651 } 652 return 653 } 654 655 for _, txn := range []bool{false, true} { 656 Convey(fmt.Sprintf("In transaction? %v", txn), func() { 657 Convey("With no namespace installed, can Put, Get, Query, and Count.", func() { 658 So(infoS.GetNamespace(c), ShouldEqual, "") 659 660 putErr, getErr, queryErr, countErr := run(c, txn) 661 So(putErr, ShouldBeNil) 662 So(getErr, ShouldBeNil) 663 So(queryErr, ShouldBeNil) 664 So(countErr, ShouldBeNil) 665 }) 666 667 Convey("With a namespace installed, can Put, Get, Query, and Count.", func() { 668 putErr, getErr, queryErr, countErr := run(infoS.MustNamespace(c, "foo"), txn) 669 So(putErr, ShouldBeNil) 670 So(getErr, ShouldBeNil) 671 So(queryErr, ShouldBeNil) 672 So(countErr, ShouldBeNil) 673 }) 674 }) 675 } 676 }) 677 678 Convey("Testable.ShowSpecialProperties", func() { 679 ds.GetTestable(c).ShowSpecialProperties(true) 680 681 var ents []Foo 682 for i := 0; i < 10; i++ { 683 ent := &Foo{} 684 So(ds.Put(c, ent), ShouldBeNil) 685 ents = append(ents, *ent) 686 } 687 So(ds.Get(c, ents), ShouldBeNil) 688 689 // Some of these entities (~50%) should have __scatter__ property 690 // populated. The algorithm is deterministic. 691 scatter := make([]string, len(ents)) 692 for i, e := range ents { 693 scatter[i] = hex.EncodeToString(e.Scatter) 694 } 695 So(scatter, ShouldResemble, []string{ 696 "d77e219d0669b1808f236ca5b25127bf8e865e3f0e68b792374526251c873c61", 697 "", 698 "", 699 "", 700 "", 701 "", 702 "b592c9de652ffc3f458910247fc16690ba2ceeef20a8566fda5dd989a5fc160e", 703 "bcefad8a2212ee1cfa3636e94264b8c73c90eaded9f429e27c7384830c1e381c", 704 "d2358c1d9e5951be7117e06eaec96a6a63090f181615e2c51afaf7f214e4d873", 705 "b29a46a6c01adb88d7001fe399d6346d5d2725b190f4fb025c9cb7c73c4ffb15", 706 }) 707 }) 708 709 Convey("Query by __scatter__", func() { 710 for i := 0; i < 100; i++ { 711 So(ds.Put(c, &Foo{}), ShouldBeNil) 712 } 713 ds.GetTestable(c).CatchupIndexes() 714 715 var ids []int64 716 So(ds.Run(c, ds.NewQuery("Foo").Order("__scatter__").Limit(5), func(f *Foo) { 717 So(f.Scatter, ShouldBeNil) // it is "invisible" 718 ids = append(ids, f.ID) 719 }), ShouldBeNil) 720 721 // Approximately "even" distribution within [1, 100] range. 722 So(ids, ShouldResemble, []int64{43, 55, 99, 23, 17}) 723 }) 724 }) 725 } 726 727 func TestCompoundIndexes(t *testing.T) { 728 t.Parallel() 729 730 idxKey := func(def ds.IndexDefinition) string { 731 So(def, ShouldNotBeNil) 732 return "idx::" + string(ds.Serialize.ToBytes(*def.PrepForIdxTable())) 733 } 734 735 Convey("Test Compound indexes", t, func() { 736 type Model struct { 737 ID int64 `gae:"$id"` 738 739 Field1 []string 740 Field2 []int64 741 } 742 743 c := Use(context.Background()) 744 t := ds.GetTestable(c).(*dsImpl) 745 head := t.data.head 746 747 So(ds.Put(c, &Model{1, []string{"hello", "world"}, []int64{10, 11}}), ShouldBeNil) 748 749 idx := ds.IndexDefinition{ 750 Kind: "Model", 751 SortBy: []ds.IndexColumn{ 752 {Property: "Field2"}, 753 }, 754 } 755 756 coll := head.Snapshot().GetCollection(idxKey(idx)) 757 So(coll, ShouldNotBeNil) 758 So(countItems(coll), ShouldEqual, 2) 759 760 idx.SortBy[0].Property = "Field1" 761 coll = head.Snapshot().GetCollection(idxKey(idx)) 762 So(coll, ShouldNotBeNil) 763 So(countItems(coll), ShouldEqual, 2) 764 765 idx.SortBy = append(idx.SortBy, ds.IndexColumn{Property: "Field1"}) 766 So(head.GetCollection(idxKey(idx)), ShouldBeNil) 767 768 t.AddIndexes(&idx) 769 coll = head.Snapshot().GetCollection(idxKey(idx)) 770 So(coll, ShouldNotBeNil) 771 So(countItems(coll), ShouldEqual, 4) 772 }) 773 } 774 775 // High level test for regression in how zero time is stored, 776 // see https://codereview.chromium.org/1334043003/ 777 func TestDefaultTimeField(t *testing.T) { 778 t.Parallel() 779 780 Convey("Default time.Time{} can be stored", t, func() { 781 type Model struct { 782 ID int64 `gae:"$id"` 783 Time time.Time 784 } 785 c := Use(context.Background()) 786 m := Model{ID: 1} 787 So(ds.Put(c, &m), ShouldBeNil) 788 789 // Reset to something non zero to ensure zero is fetched. 790 m.Time = time.Now().UTC() 791 So(ds.Get(c, &m), ShouldBeNil) 792 So(m.Time.IsZero(), ShouldBeTrue) 793 }) 794 } 795 796 func TestNewDatastore(t *testing.T) { 797 t.Parallel() 798 799 Convey("Can get and use a NewDatastore", t, func() { 800 c := UseWithAppID(context.Background(), "dev~aid") 801 c = infoS.MustNamespace(c, "ns") 802 803 dsInst := NewDatastore(c, infoS.Raw(c)) 804 c = ds.SetRaw(c, dsInst) 805 806 k := ds.MakeKey(c, "Something", 1) 807 So(k.AppID(), ShouldEqual, "dev~aid") 808 So(k.Namespace(), ShouldEqual, "ns") 809 810 type Model struct { 811 ID int64 `gae:"$id"` 812 Value []int64 813 } 814 So(ds.Put(c, &Model{ID: 1, Value: []int64{20, 30}}), ShouldBeNil) 815 816 vals := []ds.PropertyMap{} 817 So(ds.GetAll(c, ds.NewQuery("Model").Project("Value"), &vals), ShouldBeNil) 818 So(len(vals), ShouldEqual, 2) 819 820 So(vals[0].Slice("Value")[0].Value(), ShouldEqual, 20) 821 So(vals[1].Slice("Value")[0].Value(), ShouldEqual, 30) 822 }) 823 } 824 825 func TestAddIndexes(t *testing.T) { 826 t.Parallel() 827 828 Convey("Test Testable.AddIndexes", t, func() { 829 ctx := UseWithAppID(context.Background(), "aid") 830 namespaces := []string{"", "good", "news", "everyone"} 831 832 Convey("After adding datastore entries, can query against indexes in various namespaces", func() { 833 foos := []*Foo{ 834 {ID: 1, Val: 1, Name: "foo"}, 835 {ID: 2, Val: 2, Name: "bar"}, 836 {ID: 3, Val: 2, Name: "baz"}, 837 } 838 for _, ns := range namespaces { 839 So(ds.Put(infoS.MustNamespace(ctx, ns), foos), ShouldBeNil) 840 } 841 842 // Initial query, no indexes, will fail. 843 ds.GetTestable(ctx).CatchupIndexes() 844 845 var results []*Foo 846 q := ds.NewQuery("Foo").Eq("Val", 2).Gte("Name", "bar") 847 So(ds.GetAll(ctx, q, &results), ShouldErrLike, "Insufficient indexes") 848 849 // Add index for default namespace. 850 ds.GetTestable(ctx).AddIndexes(&ds.IndexDefinition{ 851 Kind: "Foo", 852 SortBy: []ds.IndexColumn{ 853 {Property: "Val"}, 854 {Property: "Name"}, 855 }, 856 }) 857 ds.GetTestable(ctx).CatchupIndexes() 858 859 for _, ns := range namespaces { 860 if ns == "" { 861 // Skip query test for empty namespace, as this is invalid. 862 continue 863 } 864 865 results = nil 866 So(ds.GetAll(infoS.MustNamespace(ctx, ns), q, &results), ShouldBeNil) 867 So(len(results), ShouldEqual, 2) 868 } 869 870 // Add "foos" to a new namespace, then confirm that it gets indexed. 871 So(ds.Put(infoS.MustNamespace(ctx, "qux"), foos), ShouldBeNil) 872 ds.GetTestable(ctx).CatchupIndexes() 873 874 results = nil 875 So(ds.GetAll(infoS.MustNamespace(ctx, "qux"), q, &results), ShouldBeNil) 876 So(len(results), ShouldEqual, 2) 877 }) 878 }) 879 } 880 881 func TestConcurrentTxn(t *testing.T) { 882 t.Parallel() 883 884 // Stress test for concurrent transactions. It transactionally increments a 885 // counter in an entity and counts how many transactions succeeded. The final 886 // counter value and the number of committed transactions should match. 887 888 Convey("Concurrent transactions work", t, func() { 889 c := Use(context.Background()) 890 891 var successes int64 892 893 for round := 0; round < 1000; round++ { 894 barrier := make(chan struct{}) 895 wg := sync.WaitGroup{} 896 897 for track := 0; track < 5; track++ { 898 wg.Add(1) 899 go func(round, track int) { 900 defer wg.Done() 901 <-barrier 902 903 err := ds.RunInTransaction(c, func(c context.Context) error { 904 ent := Foo{ID: 1} 905 switch err := ds.Get(c, &ent); { 906 case err == ds.ErrNoSuchEntity: 907 // new entity 908 case err != nil: 909 return err 910 } 911 ent.Val++ 912 return ds.Put(c, &ent) 913 }, nil) 914 if err == nil { 915 atomic.AddInt64(&successes, 1) 916 } 917 918 }(round, track) 919 } 920 921 // Run one round of the test. 922 close(barrier) 923 wg.Wait() 924 925 // Verify that everything is still ok. 926 ent := Foo{ID: 1} 927 ds.Get(c, &ent) 928 counter := atomic.LoadInt64(&successes) 929 if int64(ent.Val) != counter { // don't spam convey assertions 930 So(ent.Val, ShouldEqual, counter) 931 } 932 } 933 }) 934 }