| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library db_test; | 5 library db_test; |
| 6 | 6 |
| 7 /// NOTE: In order to run these tests, the following datastore indices must | 7 /// NOTE: In order to run these tests, the following datastore indices must |
| 8 /// exist: | 8 /// exist: |
| 9 /// $ cat index.yaml | 9 /// $ cat index.yaml |
| 10 /// indexes: | 10 /// indexes: |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 import '../../common_e2e.dart'; | 54 import '../../common_e2e.dart'; |
| 55 | 55 |
| 56 @db.Kind() | 56 @db.Kind() |
| 57 class Person extends db.Model { | 57 class Person extends db.Model { |
| 58 @db.StringProperty() | 58 @db.StringProperty() |
| 59 String name; | 59 String name; |
| 60 | 60 |
| 61 @db.IntProperty() | 61 @db.IntProperty() |
| 62 int age; | 62 int age; |
| 63 | 63 |
| 64 @db.ModelKeyProperty() | 64 @db.ModelKeyProperty(propertyName: 'mangledWife') |
| 65 db.Key wife; | 65 db.Key wife; |
| 66 | 66 |
| 67 operator==(Object other) => sameAs(other); | 67 operator==(Object other) => sameAs(other); |
| 68 | 68 |
| 69 sameAs(Object other) { | 69 sameAs(Object other) { |
| 70 return other is Person && | 70 return other is Person && |
| 71 id == other.id && | 71 id == other.id && |
| 72 parentKey == other.parentKey && | 72 parentKey == other.parentKey && |
| 73 name == other.name && | 73 name == other.name && |
| 74 age == other.age && | 74 age == other.age && |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 131 return false; | 131 return false; |
| 132 } | 132 } |
| 133 } | 133 } |
| 134 return true; | 134 return true; |
| 135 } | 135 } |
| 136 return false; | 136 return false; |
| 137 } | 137 } |
| 138 } | 138 } |
| 139 | 139 |
| 140 | 140 |
| 141 Future sleep(Duration duration) { | 141 Future sleep(Duration duration) => new Future.delayed(duration); |
| 142 var completer = new Completer(); | |
| 143 new Timer(duration, completer.complete); | |
| 144 return completer.future; | |
| 145 } | |
| 146 | 142 |
| 147 runTests(db.DatastoreDB store, String namespace) { | 143 runTests(db.DatastoreDB store, String namespace) { |
| 148 var partition = store.newPartition(namespace); | 144 var partition = store.newPartition(namespace); |
| 149 | 145 |
| 150 void compareModels(List<db.Model> expectedModels, | 146 void compareModels(List<db.Model> expectedModels, |
| 151 List<db.Model> models, | 147 List<db.Model> models, |
| 152 {bool anyOrder: false}) { | 148 {bool anyOrder: false}) { |
| 153 expect(models.length, equals(expectedModels.length)); | 149 expect(models.length, equals(expectedModels.length)); |
| 154 if (anyOrder) { | 150 if (anyOrder) { |
| 155 // Do expensive O(n^2) search. | 151 // Do expensive O(n^2) search. |
| (...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 388 for (var i = 1; i <= 10; i++) { | 384 for (var i = 1; i <= 10; i++) { |
| 389 var languages = []; | 385 var languages = []; |
| 390 if (i == 9) { | 386 if (i == 9) { |
| 391 languages = ['foo']; | 387 languages = ['foo']; |
| 392 } else if (i == 10) { | 388 } else if (i == 10) { |
| 393 languages = ['foo', 'bar']; | 389 languages = ['foo', 'bar']; |
| 394 } | 390 } |
| 395 users.add(new User() | 391 users.add(new User() |
| 396 ..id = i | 392 ..id = i |
| 397 ..parentKey = root | 393 ..parentKey = root |
| 394 ..wife = root.append(User, id: 42 + i) |
| 398 ..age = 42 + i | 395 ..age = 42 + i |
| 399 ..name = 'user$i' | 396 ..name = 'user$i' |
| 400 ..nickname = 'nickname${i%3}' | 397 ..nickname = 'nickname${i%3}' |
| 401 ..languages = languages); | 398 ..languages = languages); |
| 402 } | 399 } |
| 403 | 400 |
| 404 var expandoPersons = []; | 401 var expandoPersons = []; |
| 405 for (var i = 1; i <= 3; i++) { | 402 for (var i = 1; i <= 3; i++) { |
| 406 var expandoPerson = new ExpandoPerson() | 403 var expandoPerson = new ExpandoPerson() |
| 407 ..parentKey = root | 404 ..parentKey = root |
| (...skipping 30 matching lines...) Expand all Loading... |
| 438 | 435 |
| 439 var usersSortedAndFilteredNameDescNicknameDesc = | 436 var usersSortedAndFilteredNameDescNicknameDesc = |
| 440 usersSortedNameDescNicknameDesc.where((User u) { | 437 usersSortedNameDescNicknameDesc.where((User u) { |
| 441 return LOWER_BOUND.compareTo(u.name) <= 0; | 438 return LOWER_BOUND.compareTo(u.name) <= 0; |
| 442 }).toList(); | 439 }).toList(); |
| 443 | 440 |
| 444 var fooUsers = users.where( | 441 var fooUsers = users.where( |
| 445 (User u) => u.languages.contains('foo')).toList(); | 442 (User u) => u.languages.contains('foo')).toList(); |
| 446 var barUsers = users.where( | 443 var barUsers = users.where( |
| 447 (User u) => u.languages.contains('bar')).toList(); | 444 (User u) => u.languages.contains('bar')).toList(); |
| 445 var usersWithWife = users.where( |
| 446 (User u) => u.wife == root.append(User, id: 42 + 3)); |
| 448 | 447 |
| 449 var allInserts = [] | 448 var allInserts = [] |
| 450 ..addAll(users) | 449 ..addAll(users) |
| 451 ..addAll(expandoPersons); | 450 ..addAll(expandoPersons); |
| 452 var allKeys = allInserts.map((db.Model model) => model.key).toList(); | 451 var allKeys = allInserts.map((db.Model model) => model.key).toList(); |
| 453 return store.commit(inserts: allInserts).then((_) { | 452 return store.commit(inserts: allInserts).then((_) { |
| 454 return waitUntilEntitiesReady(store, allKeys, partition).then((_) { | 453 return waitUntilEntitiesReady(store, allKeys, partition).then((_) { |
| 455 var tests = [ | 454 var tests = [ |
| 456 // Queries for [Person] return no results, we only have [User] | 455 // Queries for [Person] return no results, we only have [User] |
| 457 // objects. | 456 // objects. |
| 458 () { | 457 () { |
| 459 return store.query(Person, partition: partition).run().toList() | 458 return store.query(Person, partition: partition).run().toList() |
| 460 .then((List<db.Model> models) { | 459 .then((List<db.Model> models) { |
| 461 compareModels([], models); | 460 compareModels([], models); |
| 462 }); | 461 }); |
| 463 }, | 462 }, |
| 464 | 463 |
| 465 // All users query | 464 // All users query |
| 466 () { | 465 () { |
| 467 return store.query(User, partition: partition).run().toList() | 466 return store.query(User, partition: partition).run().toList() |
| 468 .then((List<db.Model> models) { | 467 .then((List<db.Model> models) { |
| 469 compareModels(users, models, anyOrder: true); | 468 compareModels(users, models, anyOrder: true); |
| 470 }); | 469 }); |
| 471 }, | 470 }, |
| 472 | 471 |
| 473 // Sorted query | 472 // Sorted query |
| 474 () { | 473 () async { |
| 475 return store.query(User, partition: partition) | 474 var query = store.query(User, partition: partition) |
| 476 ..order('-name') | 475 ..order('-name') |
| 477 ..order('nickname') | 476 ..order('nickname'); |
| 478 ..run().toList().then((List<db.Model> models) { | 477 var models = await runQueryWithExponentialBackoff( |
| 479 compareModels( | 478 query, usersSortedNameDescNicknameAsc.length); |
| 480 usersSortedNameDescNicknameAsc, models); | 479 compareModels( |
| 481 }); | 480 usersSortedNameDescNicknameAsc, models); |
| 482 }, | 481 }, |
| 483 () { | 482 () async { |
| 484 return store.query(User, partition: partition) | 483 var query = store.query(User, partition: partition) |
| 485 ..order('-name') | 484 ..order('-name') |
| 486 ..order('-nickname') | 485 ..order('-nickname') |
| 487 ..run().toList().then((List<db.Model> models) { | 486 ..run(); |
| 488 compareModels( | 487 var models = await runQueryWithExponentialBackoff( |
| 489 usersSortedNameDescNicknameDesc, models); | 488 query, usersSortedNameDescNicknameDesc.length); |
| 490 }); | 489 compareModels( |
| 490 usersSortedNameDescNicknameDesc, models); |
| 491 }, | 491 }, |
| 492 | 492 |
| 493 // Sorted query with filter | 493 // Sorted query with filter |
| 494 () { | 494 () async { |
| 495 return store.query(User, partition: partition) | 495 var query = store.query(User, partition: partition) |
| 496 ..filter('name >=', LOWER_BOUND) | 496 ..filter('name >=', LOWER_BOUND) |
| 497 ..order('-name') | 497 ..order('-name') |
| 498 ..order('nickname') | 498 ..order('nickname'); |
| 499 ..run().toList().then((List<db.Model> models) { | 499 var models = await runQueryWithExponentialBackoff( |
| 500 compareModels(usersSortedAndFilteredNameDescNicknameAsc, | 500 query, usersSortedAndFilteredNameDescNicknameAsc.length); |
| 501 models); | 501 compareModels(usersSortedAndFilteredNameDescNicknameAsc, |
| 502 }); | 502 models); |
| 503 }, | 503 }, |
| 504 () { | 504 () async { |
| 505 return store.query(User, partition: partition) | 505 var query = store.query(User, partition: partition) |
| 506 ..filter('name >=', LOWER_BOUND) | 506 ..filter('name >=', LOWER_BOUND) |
| 507 ..order('-name') | 507 ..order('-name') |
| 508 ..order('-nickname') | 508 ..order('-nickname') |
| 509 ..run().toList().then((List<db.Model> models) { | 509 ..run(); |
| 510 compareModels(usersSortedAndFilteredNameDescNicknameDesc, | 510 var models = await runQueryWithExponentialBackoff( |
| 511 models); | 511 query, usersSortedAndFilteredNameDescNicknameDesc.length); |
| 512 }); | 512 compareModels(usersSortedAndFilteredNameDescNicknameDesc, |
| 513 models); |
| 513 }, | 514 }, |
| 514 | 515 |
| 515 // Filter lists | 516 // Filter lists |
| 516 /* FIXME: TODO: FIXME: "IN" not supported in public proto/apiary */ | 517 /* FIXME: TODO: FIXME: "IN" not supported in public proto/apiary */ |
| 517 () { | 518 () async { |
| 518 return store.query(User, partition: partition) | 519 var query = store.query(User, partition: partition) |
| 519 ..filter('languages IN', ['foo']) | 520 ..filter('languages IN', ['foo']) |
| 520 ..order('name') | 521 ..order('name') |
| 521 ..run().toList().then((List<db.Model> models) { | 522 ..run(); |
| 522 compareModels(fooUsers, models, anyOrder: true); | 523 var models = await runQueryWithExponentialBackoff( |
| 523 }); | 524 query, fooUsers.length); |
| 525 compareModels(fooUsers, models, anyOrder: true); |
| 524 }, | 526 }, |
| 525 () { | 527 () async { |
| 526 return store.query(User, partition: partition) | 528 var query = store.query(User, partition: partition) |
| 527 ..filter('languages IN', ['bar']) | 529 ..filter('languages IN', ['bar']) |
| 528 ..order('name') | 530 ..order('name') |
| 529 ..run().toList().then((List<db.Model> models) { | 531 ..run(); |
| 530 compareModels(barUsers, models, anyOrder: true); | 532 var models = await runQueryWithExponentialBackoff( |
| 531 }); | 533 query, barUsers.length); |
| 534 compareModels(barUsers, models, anyOrder: true); |
| 535 }, |
| 536 |
| 537 // Filter equals |
| 538 () async { |
| 539 var wifeKey = root.append(User, id: usersWithWife.first.wife.id); |
| 540 var query = store.query(User, partition: partition) |
| 541 ..filter('wife =', wifeKey) |
| 542 ..run(); |
| 543 var models = await runQueryWithExponentialBackoff( |
| 544 query, usersWithWife.length); |
| 545 compareModels(usersWithWife, models, anyOrder: true); |
| 532 }, | 546 }, |
| 533 | 547 |
| 534 // Simple limit/offset test. | 548 // Simple limit/offset test. |
| 535 () { | 549 () async { |
| 536 return store.query(User, partition: partition) | 550 var query = store.query(User, partition: partition) |
| 537 ..order('-name') | 551 ..order('-name') |
| 538 ..order('nickname') | 552 ..order('nickname') |
| 539 ..offset(3) | 553 ..offset(3) |
| 540 ..limit(4) | 554 ..limit(4); |
| 541 ..run().toList().then((List<db.Model> models) { | 555 var expectedModels = |
| 542 var expectedModels = | 556 usersSortedAndFilteredNameDescNicknameAsc.sublist(3, 7); |
| 543 usersSortedAndFilteredNameDescNicknameAsc.sublist(3, 7); | 557 var models = await runQueryWithExponentialBackoff( |
| 544 compareModels(expectedModels, models); | 558 query, expectedModels.length); |
| 545 }); | 559 compareModels(expectedModels, models); |
| 546 }, | 560 }, |
| 547 | 561 |
| 548 // Expando queries: Filter on normal property. | 562 // Expando queries: Filter on normal property. |
| 549 () { | 563 () async { |
| 550 return store.query(ExpandoPerson, partition: partition) | 564 var query = store.query(ExpandoPerson, partition: partition) |
| 551 ..filter('name =', expandoPersons.last.name) | 565 ..filter('name =', expandoPersons.last.name) |
| 552 ..run().toList().then((List<db.Model> models) { | 566 ..run(); |
| 553 compareModels([expandoPersons.last], models); | 567 var models = await runQueryWithExponentialBackoff(query, 1); |
| 554 }); | 568 compareModels([expandoPersons.last], models); |
| 555 }, | 569 }, |
| 556 // Expando queries: Filter on expanded String property | 570 // Expando queries: Filter on expanded String property |
| 557 () { | 571 () async { |
| 558 return store.query(ExpandoPerson, partition: partition) | 572 var query = store.query(ExpandoPerson, partition: partition) |
| 559 ..filter('foo =', expandoPersons.last.foo) | 573 ..filter('foo =', expandoPersons.last.foo) |
| 560 ..run().toList().then((List<db.Model> models) { | 574 ..run(); |
| 561 compareModels([expandoPersons.last], models); | 575 var models = await runQueryWithExponentialBackoff(query, 1); |
| 562 }); | 576 compareModels([expandoPersons.last], models); |
| 563 }, | 577 }, |
| 564 // Expando queries: Filter on expanded int property | 578 // Expando queries: Filter on expanded int property |
| 565 () { | 579 () async { |
| 566 return store.query(ExpandoPerson, partition: partition) | 580 var query = store.query(ExpandoPerson, partition: partition) |
| 567 ..filter('bar =', expandoPersons.last.bar) | 581 ..filter('bar =', expandoPersons.last.bar) |
| 568 ..run().toList().then((List<db.Model> models) { | 582 ..run(); |
| 569 compareModels([expandoPersons.last], models); | 583 var models = await runQueryWithExponentialBackoff(query, 1); |
| 570 }); | 584 compareModels([expandoPersons.last], models); |
| 571 }, | 585 }, |
| 572 // Expando queries: Filter normal property with different | 586 // Expando queries: Filter normal property with different |
| 573 // propertyName (datastore name is 'NN'). | 587 // propertyName (datastore name is 'NN'). |
| 574 () { | 588 () async { |
| 575 return store.query(ExpandoPerson, partition: partition) | 589 var query = store.query(ExpandoPerson, partition: partition) |
| 576 ..filter('nickname =', expandoPersons.last.nickname) | 590 ..filter('nickname =', expandoPersons.last.nickname) |
| 577 ..run().toList().then((List<db.Model> models) { | 591 ..run(); |
| 578 compareModels([expandoPersons.last], models); | 592 var models = await runQueryWithExponentialBackoff(query, 1); |
| 579 }); | 593 compareModels([expandoPersons.last], models); |
| 580 }, | 594 }, |
| 581 | 595 |
| 582 // Delete results | 596 // Delete results |
| 583 () => store.commit(deletes: allKeys), | 597 () => store.commit(deletes: allKeys), |
| 584 | 598 |
| 585 // Wait until the entity deletes are reflected in the indices. | 599 // Wait until the entity deletes are reflected in the indices. |
| 586 () => waitUntilEntitiesGone(store, allKeys, partition), | 600 () => waitUntilEntitiesGone(store, allKeys, partition), |
| 587 | 601 |
| 588 // Make sure queries don't return results | 602 // Make sure queries don't return results |
| 589 () => store.lookup(allKeys).then((List<db.Model> models) { | 603 () => store.lookup(allKeys).then((List<db.Model> models) { |
| 590 expect(models.length, equals(allKeys.length)); | 604 expect(models.length, equals(allKeys.length)); |
| 591 for (var model in models) { | 605 for (var model in models) { |
| 592 expect(model, isNull); | 606 expect(model, isNull); |
| 593 } | 607 } |
| 594 }), | 608 }), |
| 595 ]; | 609 ]; |
| 596 return Future.forEach(tests, (f) => f()); | 610 return Future.forEach(tests, (f) => f()); |
| 597 }); | 611 }); |
| 598 }); | 612 }); |
| 599 }); | 613 }); |
| 600 }); | 614 }); |
| 601 } | 615 } |
| 602 | 616 |
| 617 Future<List<db.Model>> runQueryWithExponentialBackoff( |
| 618 db.Query query, int expectedResults) async { |
| 619 for (int i = 0; i <= 6; i++) { |
| 620 if (i > 0) { |
| 621 // Wait for 0.1s, 0.2s, ..., 12.8s |
| 622 var duration = new Duration(milliseconds: 100 * (2 << i)); |
| 623 print("Running query did return less results than expected." |
| 624 "Using exponential backoff: Sleeping for $duration."); |
| 625 await sleep(duration); |
| 626 } |
| 627 |
| 628 List<db.Model> models = await query.run().toList(); |
| 629 if (models.length >= expectedResults) { |
| 630 return models; |
| 631 } |
| 632 } |
| 633 |
| 634 throw new Exception( |
| 635 "Tried running a query with exponential backoff, giving up now."); |
| 636 } |
| 637 |
| 603 Future waitUntilEntitiesReady(db.DatastoreDB mdb, | 638 Future waitUntilEntitiesReady(db.DatastoreDB mdb, |
| 604 List<db.Key> keys, | 639 List<db.Key> keys, |
| 605 db.Partition partition) { | 640 db.Partition partition) { |
| 606 return waitUntilEntitiesHelper(mdb, keys, true, partition); | 641 return waitUntilEntitiesHelper(mdb, keys, true, partition); |
| 607 } | 642 } |
| 608 | 643 |
| 609 Future waitUntilEntitiesGone(db.DatastoreDB mdb, | 644 Future waitUntilEntitiesGone(db.DatastoreDB mdb, |
| 610 List<db.Key> keys, | 645 List<db.Key> keys, |
| 611 db.Partition partition) { | 646 db.Partition partition) { |
| 612 return waitUntilEntitiesHelper(mdb, keys, false, partition); | 647 return waitUntilEntitiesHelper(mdb, keys, false, partition); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 648 var scopes = datastore_impl.DatastoreImpl.SCOPES; | 683 var scopes = datastore_impl.DatastoreImpl.SCOPES; |
| 649 | 684 |
| 650 withAuthClient(scopes, (String project, httpClient) { | 685 withAuthClient(scopes, (String project, httpClient) { |
| 651 var datastore = new datastore_impl.DatastoreImpl(httpClient, 's~$project'); | 686 var datastore = new datastore_impl.DatastoreImpl(httpClient, 's~$project'); |
| 652 return datastore_test.cleanupDB(datastore, null).then((_) { | 687 return datastore_test.cleanupDB(datastore, null).then((_) { |
| 653 return runE2EUnittest( | 688 return runE2EUnittest( |
| 654 () => runTests(new db.DatastoreDB(datastore), null)); | 689 () => runTests(new db.DatastoreDB(datastore), null)); |
| 655 }); | 690 }); |
| 656 }); | 691 }); |
| 657 } | 692 } |
| OLD | NEW |