mirror of
https://github.com/status-im/realm-js.git
synced 2025-01-12 07:14:23 +00:00
632d757014
If there are multiple Realm instances for a single file on a single thread due to disabling caching we need to actually deliver the results to the appropriate SharedGroup for each notifier rather than delivering them all to the first one.
446 lines
14 KiB
C++
446 lines
14 KiB
C++
#include "catch.hpp"
|
|
|
|
#include "util/test_file.hpp"
|
|
#include "util/index_helpers.hpp"
|
|
|
|
#include "binding_context.hpp"
|
|
#include "list.hpp"
|
|
#include "object_schema.hpp"
|
|
#include "property.hpp"
|
|
#include "results.hpp"
|
|
#include "schema.hpp"
|
|
|
|
#include "impl/realm_coordinator.hpp"
|
|
|
|
#include <realm/commit_log.hpp>
|
|
#include <realm/group_shared.hpp>
|
|
#include <realm/link_view.hpp>
|
|
|
|
using namespace realm;
|
|
|
|
TEST_CASE("list") {
|
|
InMemoryTestFile config;
|
|
config.automatic_change_notifications = false;
|
|
config.cache = false;
|
|
config.schema = std::make_unique<Schema>(Schema{
|
|
{"origin", "", {
|
|
{"array", PropertyTypeArray, "target"}
|
|
}},
|
|
{"target", "", {
|
|
{"value", PropertyTypeInt}
|
|
}},
|
|
{"other_origin", "", {
|
|
{"array", PropertyTypeArray, "other_target"}
|
|
}},
|
|
{"other_target", "", {
|
|
{"value", PropertyTypeInt}
|
|
}},
|
|
});
|
|
|
|
auto r = Realm::get_shared_realm(config);
|
|
auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path);
|
|
|
|
auto origin = r->read_group()->get_table("class_origin");
|
|
auto target = r->read_group()->get_table("class_target");
|
|
|
|
r->begin_transaction();
|
|
|
|
target->add_empty_row(10);
|
|
for (int i = 0; i < 10; ++i)
|
|
target->set_int(0, i, i);
|
|
|
|
origin->add_empty_row(2);
|
|
LinkViewRef lv = origin->get_linklist(0, 0);
|
|
for (int i = 0; i < 10; ++i)
|
|
lv->add(i);
|
|
LinkViewRef lv2 = origin->get_linklist(0, 1);
|
|
for (int i = 0; i < 10; ++i)
|
|
lv2->add(i);
|
|
|
|
r->commit_transaction();
|
|
|
|
SECTION("add_notification_block()") {
|
|
CollectionChangeSet change;
|
|
List lst(r, *r->config().schema->find("origin"), lv);
|
|
|
|
auto write = [&](auto&& f) {
|
|
r->begin_transaction();
|
|
f();
|
|
r->commit_transaction();
|
|
|
|
advance_and_notify(*r);
|
|
};
|
|
|
|
auto require_change = [&] {
|
|
auto token = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
|
|
change = c;
|
|
});
|
|
advance_and_notify(*r);
|
|
return token;
|
|
};
|
|
|
|
auto require_no_change = [&] {
|
|
bool first = true;
|
|
auto token = lst.add_notification_callback([&, first](CollectionChangeSet c, std::exception_ptr err) mutable {
|
|
REQUIRE(first);
|
|
first = false;
|
|
});
|
|
advance_and_notify(*r);
|
|
return token;
|
|
};
|
|
|
|
SECTION("modifying the list sends a change notifications") {
|
|
auto token = require_change();
|
|
write([&] { lst.remove(5); });
|
|
REQUIRE_INDICES(change.deletions, 5);
|
|
}
|
|
|
|
SECTION("modifying a different list doesn't send a change notification") {
|
|
auto token = require_no_change();
|
|
write([&] { lv2->remove(5); });
|
|
}
|
|
|
|
SECTION("deleting the list sends a change notification") {
|
|
auto token = require_change();
|
|
write([&] { origin->move_last_over(0); });
|
|
REQUIRE_INDICES(change.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
}
|
|
|
|
SECTION("modifying one of the target rows sends a change notification") {
|
|
auto token = require_change();
|
|
write([&] { lst.get(5).set_int(0, 6); });
|
|
REQUIRE_INDICES(change.modifications, 5);
|
|
}
|
|
|
|
SECTION("deleting a target row sends a change notification") {
|
|
auto token = require_change();
|
|
write([&] { target->move_last_over(5); });
|
|
REQUIRE_INDICES(change.deletions, 5);
|
|
}
|
|
|
|
SECTION("adding a row and then modifying the target row does not mark the row as modified") {
|
|
auto token = require_change();
|
|
write([&] {
|
|
lst.add(5);
|
|
target->set_int(0, 5, 10);
|
|
});
|
|
REQUIRE_INDICES(change.insertions, 10);
|
|
REQUIRE_INDICES(change.modifications, 5);
|
|
}
|
|
|
|
SECTION("modifying and then moving a row reports move/insert but not modification") {
|
|
auto token = require_change();
|
|
write([&] {
|
|
target->set_int(0, 5, 10);
|
|
lst.move(5, 8);
|
|
});
|
|
REQUIRE_INDICES(change.insertions, 8);
|
|
REQUIRE_INDICES(change.deletions, 5);
|
|
REQUIRE_MOVES(change, {5, 8});
|
|
REQUIRE(change.modifications.empty());
|
|
}
|
|
|
|
SECTION("modifying a row which appears multiple times in a list marks them all as modified") {
|
|
r->begin_transaction();
|
|
lst.add(5);
|
|
r->commit_transaction();
|
|
|
|
auto token = require_change();
|
|
write([&] { target->set_int(0, 5, 10); });
|
|
REQUIRE_INDICES(change.modifications, 5, 10);
|
|
}
|
|
|
|
SECTION("deleting a row which appears multiple times in a list marks them all as modified") {
|
|
r->begin_transaction();
|
|
lst.add(5);
|
|
r->commit_transaction();
|
|
|
|
auto token = require_change();
|
|
write([&] { target->move_last_over(5); });
|
|
REQUIRE_INDICES(change.deletions, 5, 10);
|
|
}
|
|
|
|
SECTION("clearing the target table sends a change notification") {
|
|
auto token = require_change();
|
|
write([&] { target->clear(); });
|
|
REQUIRE_INDICES(change.deletions, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
}
|
|
|
|
SECTION("moving a target row does not send a change notification") {
|
|
// Remove a row from the LV so that we have one to delete that's not in the list
|
|
r->begin_transaction();
|
|
lv->remove(2);
|
|
r->commit_transaction();
|
|
|
|
auto token = require_no_change();
|
|
write([&] { target->move_last_over(2); });
|
|
}
|
|
|
|
SECTION("multiple LinkViws for the same LinkList can get notifications") {
|
|
r->begin_transaction();
|
|
target->clear();
|
|
target->add_empty_row(5);
|
|
r->commit_transaction();
|
|
|
|
auto get_list = [&] {
|
|
auto r = Realm::get_shared_realm(config);
|
|
auto lv = r->read_group()->get_table("class_origin")->get_linklist(0, 0);
|
|
return List(r, *r->config().schema->find("origin"), lv);
|
|
};
|
|
auto change_list = [&] {
|
|
r->begin_transaction();
|
|
if (lv->size()) {
|
|
target->set_int(0, lv->size() - 1, lv->size());
|
|
}
|
|
lv->add(lv->size());
|
|
r->commit_transaction();
|
|
};
|
|
|
|
List lists[3];
|
|
NotificationToken tokens[3];
|
|
CollectionChangeSet changes[3];
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
lists[i] = get_list();
|
|
tokens[i] = lists[i].add_notification_callback([i, &changes](CollectionChangeSet c, std::exception_ptr) {
|
|
changes[i] = std::move(c);
|
|
});
|
|
change_list();
|
|
}
|
|
|
|
// Each of the Lists now has a different source version and state at
|
|
// that version, so they should all see different changes despite
|
|
// being for the same LinkList
|
|
for (auto& list : lists)
|
|
advance_and_notify(*list.get_realm());
|
|
|
|
REQUIRE_INDICES(changes[0].insertions, 0, 1, 2);
|
|
REQUIRE(changes[0].modifications.empty());
|
|
|
|
REQUIRE_INDICES(changes[1].insertions, 1, 2);
|
|
REQUIRE_INDICES(changes[1].modifications, 0);
|
|
|
|
REQUIRE_INDICES(changes[2].insertions, 2);
|
|
REQUIRE_INDICES(changes[2].modifications, 1);
|
|
|
|
// After making another change, they should all get the same notification
|
|
change_list();
|
|
for (auto& list : lists)
|
|
advance_and_notify(*list.get_realm());
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
REQUIRE_INDICES(changes[i].insertions, 3);
|
|
REQUIRE_INDICES(changes[i].modifications, 2);
|
|
}
|
|
}
|
|
|
|
SECTION("tables-of-interest are tracked properly for multiple source versions") {
|
|
auto other_origin = r->read_group()->get_table("class_other_origin");
|
|
auto other_target = r->read_group()->get_table("class_other_target");
|
|
|
|
r->begin_transaction();
|
|
other_target->add_empty_row();
|
|
other_origin->add_empty_row();
|
|
LinkViewRef lv2 = other_origin->get_linklist(0, 0);
|
|
lv2->add(0);
|
|
r->commit_transaction();
|
|
|
|
List lst2(r, *r->config().schema->find("other_origin"), lv2);
|
|
|
|
// Add a callback for list1, advance the version, then add a
|
|
// callback for list2, so that the notifiers added at each source
|
|
// version have different tables watched for modifications
|
|
CollectionChangeSet changes1, changes2;
|
|
auto token1 = lst.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
|
|
changes1 = std::move(c);
|
|
});
|
|
|
|
r->begin_transaction(); r->commit_transaction();
|
|
|
|
auto token2 = lst2.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
|
|
changes2 = std::move(c);
|
|
});
|
|
|
|
r->begin_transaction();
|
|
target->set_int(0, 0, 10);
|
|
r->commit_transaction();
|
|
advance_and_notify(*r);
|
|
|
|
REQUIRE_INDICES(changes1.modifications, 0);
|
|
REQUIRE(changes2.empty());
|
|
}
|
|
|
|
SECTION("modifications are reported for rows that are moved and then moved back in a second transaction") {
|
|
auto token = require_change();
|
|
|
|
r->begin_transaction();
|
|
lv->get(5).set_int(0, 10);
|
|
lv->get(1).set_int(0, 10);
|
|
lv->move(5, 8);
|
|
lv->move(1, 2);
|
|
r->commit_transaction();
|
|
|
|
coordinator.on_change();
|
|
|
|
write([&]{
|
|
lv->move(8, 5);
|
|
});
|
|
|
|
REQUIRE_INDICES(change.deletions, 1);
|
|
REQUIRE_INDICES(change.insertions, 2);
|
|
REQUIRE_INDICES(change.modifications, 5);
|
|
REQUIRE_MOVES(change, {1, 2});
|
|
}
|
|
}
|
|
|
|
SECTION("sorted add_notification_block()") {
|
|
List lst(r, *r->config().schema->find("origin"), lv);
|
|
Results results = lst.sort({{0}, {false}});
|
|
|
|
int notification_calls = 0;
|
|
CollectionChangeSet change;
|
|
auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
|
|
REQUIRE_FALSE(err);
|
|
change = c;
|
|
++notification_calls;
|
|
});
|
|
|
|
advance_and_notify(*r);
|
|
|
|
auto write = [&](auto&& f) {
|
|
r->begin_transaction();
|
|
f();
|
|
r->commit_transaction();
|
|
|
|
advance_and_notify(*r);
|
|
};
|
|
|
|
SECTION("add duplicates") {
|
|
write([&] {
|
|
lst.add(5);
|
|
lst.add(5);
|
|
lst.add(5);
|
|
});
|
|
REQUIRE(notification_calls == 2);
|
|
REQUIRE_INDICES(change.insertions, 5, 6, 7);
|
|
}
|
|
|
|
SECTION("change order by modifying target") {
|
|
write([&] {
|
|
lst.get(5).set_int(0, 15);
|
|
});
|
|
REQUIRE(notification_calls == 2);
|
|
REQUIRE_INDICES(change.deletions, 4);
|
|
REQUIRE_INDICES(change.insertions, 0);
|
|
}
|
|
|
|
SECTION("swap") {
|
|
write([&] {
|
|
lst.swap(1, 2);
|
|
});
|
|
REQUIRE(notification_calls == 1);
|
|
}
|
|
|
|
SECTION("move") {
|
|
write([&] {
|
|
lst.move(5, 3);
|
|
});
|
|
REQUIRE(notification_calls == 1);
|
|
}
|
|
}
|
|
|
|
SECTION("filtered add_notification_block()") {
|
|
List lst(r, *r->config().schema->find("origin"), lv);
|
|
Results results = lst.filter(target->where().less(0, 9));
|
|
|
|
int notification_calls = 0;
|
|
CollectionChangeSet change;
|
|
auto token = results.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr err) {
|
|
REQUIRE_FALSE(err);
|
|
change = c;
|
|
++notification_calls;
|
|
});
|
|
|
|
advance_and_notify(*r);
|
|
|
|
auto write = [&](auto&& f) {
|
|
r->begin_transaction();
|
|
f();
|
|
r->commit_transaction();
|
|
|
|
advance_and_notify(*r);
|
|
};
|
|
|
|
SECTION("add duplicates") {
|
|
write([&] {
|
|
lst.add(5);
|
|
lst.add(5);
|
|
lst.add(5);
|
|
});
|
|
REQUIRE(notification_calls == 2);
|
|
REQUIRE_INDICES(change.insertions, 9, 10, 11);
|
|
}
|
|
|
|
SECTION("swap") {
|
|
write([&] {
|
|
lst.swap(1, 2);
|
|
});
|
|
REQUIRE(notification_calls == 2);
|
|
REQUIRE_INDICES(change.deletions, 2);
|
|
REQUIRE_INDICES(change.insertions, 1);
|
|
|
|
write([&] {
|
|
lst.swap(5, 8);
|
|
});
|
|
REQUIRE(notification_calls == 3);
|
|
REQUIRE_INDICES(change.deletions, 5, 8);
|
|
REQUIRE_INDICES(change.insertions, 5, 8);
|
|
}
|
|
|
|
SECTION("move") {
|
|
write([&] {
|
|
lst.move(5, 3);
|
|
});
|
|
REQUIRE(notification_calls == 2);
|
|
REQUIRE_INDICES(change.deletions, 5);
|
|
REQUIRE_INDICES(change.insertions, 3);
|
|
}
|
|
|
|
SECTION("move non-matching entry") {
|
|
write([&] {
|
|
lst.move(9, 3);
|
|
});
|
|
REQUIRE(notification_calls == 1);
|
|
}
|
|
}
|
|
|
|
SECTION("sort()") {
|
|
auto objectschema = &*r->config().schema->find("origin");
|
|
List list(r, *objectschema, lv);
|
|
auto results = list.sort({{0}, {false}});
|
|
|
|
REQUIRE(&results.get_object_schema() == objectschema);
|
|
REQUIRE(results.get_mode() == Results::Mode::LinkView);
|
|
REQUIRE(results.size() == 10);
|
|
REQUIRE(results.sum(0) == 45);
|
|
|
|
for (size_t i = 0; i < 10; ++i) {
|
|
REQUIRE(results.get(i).get_index() == 9 - i);
|
|
}
|
|
}
|
|
|
|
SECTION("filter()") {
|
|
auto objectschema = &*r->config().schema->find("origin");
|
|
List list(r, *objectschema, lv);
|
|
auto results = list.filter(target->where().greater(0, 5));
|
|
|
|
REQUIRE(&results.get_object_schema() == objectschema);
|
|
REQUIRE(results.get_mode() == Results::Mode::Query);
|
|
REQUIRE(results.size() == 4);
|
|
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
REQUIRE(results.get(i).get_index() == i + 6);
|
|
}
|
|
}
|
|
}
|