123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2017 Realm Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- ////////////////////////////////////////////////////////////////////////////
- #include "catch.hpp"
- #include "util/event_loop.hpp"
- #include "util/index_helpers.hpp"
- #include "util/test_file.hpp"
- #include "feature_checks.hpp"
- #include "collection_notifications.hpp"
- #include "object_accessor.hpp"
- #include "property.hpp"
- #include "schema.hpp"
- #include "impl/realm_coordinator.hpp"
- #include "impl/object_accessor_impl.hpp"
- #include <realm/group_shared.hpp>
- #include <realm/util/any.hpp>
- #include <cstdint>
- using namespace realm;
- namespace {
- using AnyDict = std::map<std::string, util::Any>;
- using AnyVec = std::vector<util::Any>;
- }
- struct TestContext : CppContext {
- std::map<std::string, AnyDict> defaults;
- using CppContext::CppContext;
- TestContext(TestContext& parent, realm::Property const& prop)
- : CppContext(parent, prop)
- , defaults(parent.defaults)
- { }
- util::Optional<util::Any>
- default_value_for_property(ObjectSchema const& object, Property const& prop)
- {
- auto obj_it = defaults.find(object.name);
- if (obj_it == defaults.end())
- return util::none;
- auto prop_it = obj_it->second.find(prop.name);
- if (prop_it == obj_it->second.end())
- return util::none;
- return prop_it->second;
- }
- void will_change(Object const&, Property const&) {}
- void did_change() {}
- std::string print(util::Any) { return "not implemented"; }
- bool allow_missing(util::Any) { return false; }
- };
- TEST_CASE("object") {
- using namespace std::string_literals;
- _impl::RealmCoordinator::assert_no_open_realms();
- InMemoryTestFile config;
- config.automatic_change_notifications = false;
- config.cache = false;
- config.schema = Schema{
- {"table", {
- {"value 1", PropertyType::Int},
- {"value 2", PropertyType::Int},
- }},
- {"all types", {
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- {"bool", PropertyType::Bool},
- {"int", PropertyType::Int},
- {"float", PropertyType::Float},
- {"double", PropertyType::Double},
- {"string", PropertyType::String},
- {"data", PropertyType::Data},
- {"date", PropertyType::Date},
- {"object", PropertyType::Object|PropertyType::Nullable, "link target"},
- {"bool array", PropertyType::Array|PropertyType::Bool},
- {"int array", PropertyType::Array|PropertyType::Int},
- {"float array", PropertyType::Array|PropertyType::Float},
- {"double array", PropertyType::Array|PropertyType::Double},
- {"string array", PropertyType::Array|PropertyType::String},
- {"data array", PropertyType::Array|PropertyType::Data},
- {"date array", PropertyType::Array|PropertyType::Date},
- {"object array", PropertyType::Array|PropertyType::Object, "array target"},
- }},
- {"all optional types", {
- {"pk", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{true}},
- {"bool", PropertyType::Bool|PropertyType::Nullable},
- {"int", PropertyType::Int|PropertyType::Nullable},
- {"float", PropertyType::Float|PropertyType::Nullable},
- {"double", PropertyType::Double|PropertyType::Nullable},
- {"string", PropertyType::String|PropertyType::Nullable},
- {"data", PropertyType::Data|PropertyType::Nullable},
- {"date", PropertyType::Date|PropertyType::Nullable},
- {"bool array", PropertyType::Array|PropertyType::Bool|PropertyType::Nullable},
- {"int array", PropertyType::Array|PropertyType::Int|PropertyType::Nullable},
- {"float array", PropertyType::Array|PropertyType::Float|PropertyType::Nullable},
- {"double array", PropertyType::Array|PropertyType::Double|PropertyType::Nullable},
- {"string array", PropertyType::Array|PropertyType::String|PropertyType::Nullable},
- {"data array", PropertyType::Array|PropertyType::Data|PropertyType::Nullable},
- {"date array", PropertyType::Array|PropertyType::Date|PropertyType::Nullable},
- }},
- {"link target", {
- {"value", PropertyType::Int},
- }, {
- {"origin", PropertyType::LinkingObjects|PropertyType::Array, "all types", "object"},
- }},
- {"array target", {
- {"value", PropertyType::Int},
- }},
- {"pk after list", {
- {"array 1", PropertyType::Array|PropertyType::Object, "array target"},
- {"int 1", PropertyType::Int},
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- {"int 2", PropertyType::Int},
- {"array 2", PropertyType::Array|PropertyType::Object, "array target"},
- }},
- {"nullable int pk", {
- {"pk", PropertyType::Int|PropertyType::Nullable, Property::IsPrimary{true}},
- }},
- {"nullable string pk", {
- {"pk", PropertyType::String|PropertyType::Nullable, Property::IsPrimary{true}},
- }},
- {"person", {
- {"name", PropertyType::String, Property::IsPrimary{true}},
- {"age", PropertyType::Int},
- {"scores", PropertyType::Array|PropertyType::Int},
- {"assistant", PropertyType::Object|PropertyType::Nullable, "person"},
- {"team", PropertyType::Array|PropertyType::Object, "person"},
- }},
- };
- config.schema_version = 0;
- auto r = Realm::get_shared_realm(config);
- auto& coordinator = *_impl::RealmCoordinator::get_existing_coordinator(config.path);
- SECTION("add_notification_callback()") {
- auto table = r->read_group().get_table("class_table");
- r->begin_transaction();
- table->add_empty_row(10);
- for (int i = 0; i < 10; ++i)
- table->set_int(0, i, i);
- r->commit_transaction();
- auto r2 = coordinator.get_realm();
- CollectionChangeSet change;
- Row row = table->get(0);
- Object object(r, *r->schema().find("table"), row);
- auto write = [&](auto&& f) {
- r->begin_transaction();
- f();
- r->commit_transaction();
- advance_and_notify(*r);
- };
- auto require_change = [&] {
- auto token = object.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
- change = c;
- });
- advance_and_notify(*r);
- return token;
- };
- auto require_no_change = [&] {
- bool first = true;
- auto token = object.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- REQUIRE(first);
- first = false;
- });
- advance_and_notify(*r);
- return token;
- };
- SECTION("deleting the object sends a change notification") {
- auto token = require_change();
- write([&] { row.move_last_over(); });
- REQUIRE_INDICES(change.deletions, 0);
- }
- SECTION("modifying the object sends a change notification") {
- auto token = require_change();
- write([&] { row.set_int(0, 10); });
- REQUIRE_INDICES(change.modifications, 0);
- REQUIRE(change.columns.size() == 1);
- REQUIRE_INDICES(change.columns[0], 0);
- write([&] { row.set_int(1, 10); });
- REQUIRE_INDICES(change.modifications, 0);
- REQUIRE(change.columns.size() == 2);
- REQUIRE(change.columns[0].empty());
- REQUIRE_INDICES(change.columns[1], 0);
- }
- SECTION("modifying a different object") {
- auto token = require_no_change();
- write([&] { table->get(1).set_int(0, 10); });
- }
- SECTION("moving the object") {
- auto token = require_no_change();
- write([&] { table->swap_rows(0, 5); });
- }
- SECTION("subsuming the object") {
- auto token = require_change();
- write([&] {
- table->insert_empty_row(0);
- table->merge_rows(row.get_index(), 0);
- row.set_int(0, 10);
- });
- REQUIRE(change.columns.size() == 1);
- REQUIRE_INDICES(change.columns[0], 0);
- }
- SECTION("multiple write transactions") {
- auto token = require_change();
- auto r2row = r2->read_group().get_table("class_table")->get(0);
- r2->begin_transaction();
- r2row.set_int(0, 1);
- r2->commit_transaction();
- r2->begin_transaction();
- r2row.set_int(1, 2);
- r2->commit_transaction();
- advance_and_notify(*r);
- REQUIRE(change.columns.size() == 2);
- REQUIRE_INDICES(change.columns[0], 0);
- REQUIRE_INDICES(change.columns[1], 0);
- }
- SECTION("skipping a notification") {
- auto token = require_no_change();
- write([&] {
- row.set_int(0, 1);
- token.suppress_next();
- });
- }
- SECTION("skipping only effects the current transaction even if no notification would occur anyway") {
- auto token = require_change();
- // would not produce a notification even if it wasn't skipped because no changes were made
- write([&] {
- token.suppress_next();
- });
- REQUIRE(change.empty());
- // should now produce a notification
- write([&] {
- row.set_int(0, 1);
- });
- REQUIRE_INDICES(change.modifications, 0);
- }
- SECTION("add notification callback, remove it, then add another notification callback") {
- {
- auto token = object.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- FAIL("This should never happen");
- });
- }
- auto token = require_change();
- write([&] { row.move_last_over(); });
- REQUIRE_INDICES(change.deletions, 0);
- }
- SECTION("observing deleted object throws") {
- write([&] {
- row.move_last_over();
- });
- REQUIRE_THROWS(require_change());
- }
- }
- TestContext d(r);
- auto create = [&](util::Any&& value, bool update, bool update_only_diff = false) {
- r->begin_transaction();
- auto obj = Object::create(d, r, *r->schema().find("all types"), value, update, update_only_diff);
- r->commit_transaction();
- return obj;
- };
- auto create_sub = [&](util::Any&& value, bool update, bool update_only_diff = false) {
- r->begin_transaction();
- auto obj = Object::create(d, r, *r->schema().find("link target"), value, update, update_only_diff);
- r->commit_transaction();
- return obj;
- };
- auto create_company = [&](util::Any&& value, bool update, bool update_only_diff = false) {
- r->begin_transaction();
- auto obj = Object::create(d, r, *r->schema().find("person"), value, update, update_only_diff);
- r->commit_transaction();
- return obj;
- };
- SECTION("create object") {
- auto obj = create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(10)}}},
- {"bool array", AnyVec{true, false}},
- {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
- {"float array", AnyVec{1.1f, 2.2f}},
- {"double array", AnyVec{3.3, 4.4}},
- {"string array", AnyVec{"a"s, "b"s, "c"s}},
- {"data array", AnyVec{"d"s, "e"s, "f"s}},
- {"date array", AnyVec{}},
- {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
- }, false);
- auto row = obj.row();
- REQUIRE(row.get_int(0) == 1);
- REQUIRE(row.get_bool(1) == true);
- REQUIRE(row.get_int(2) == 5);
- REQUIRE(row.get_float(3) == 2.2f);
- REQUIRE(row.get_double(4) == 3.3);
- REQUIRE(row.get_string(5) == "hello");
- REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
- REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
- REQUIRE(row.get_link(8) == 0);
- auto link_target = r->read_group().get_table("class_link target")->get(0);
- REQUIRE(link_target.get_int(0) == 10);
- auto check_array = [&](size_t col, auto... values) {
- auto table = row.get_subtable(col);
- size_t i = 0;
- for (auto& value : {values...}) {
- CAPTURE(i);
- REQUIRE(i < row.get_subtable_size(col));
- REQUIRE(value == table->get<typename std::decay<decltype(value)>::type>(0, i));
- ++i;
- }
- };
- check_array(9, true, false);
- check_array(10, INT64_C(5), INT64_C(6));
- check_array(11, 1.1f, 2.2f);
- check_array(12, 3.3, 4.4);
- check_array(13, StringData("a"), StringData("b"), StringData("c"));
- check_array(14, BinaryData("d", 1), BinaryData("e", 1), BinaryData("f", 1));
- auto list = row.get_linklist(16);
- REQUIRE(list->size() == 1);
- REQUIRE(list->get(0).get_int(0) == 20);
- }
- SECTION("create uses defaults for missing values") {
- d.defaults["all types"] = {
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(10)}}},
- {"bool array", AnyVec{true, false}},
- {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
- {"float array", AnyVec{1.1f, 2.2f}},
- {"double array", AnyVec{3.3, 4.4}},
- {"string array", AnyVec{"a"s, "b"s, "c"s}},
- {"data array", AnyVec{"d"s, "e"s, "f"s}},
- {"date array", AnyVec{}},
- {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
- };
- auto obj = create(AnyDict{
- {"pk", INT64_C(1)},
- {"float", 6.6f},
- }, false);
- auto row = obj.row();
- REQUIRE(row.get_int(0) == 1);
- REQUIRE(row.get_bool(1) == true);
- REQUIRE(row.get_int(2) == 5);
- REQUIRE(row.get_float(3) == 6.6f);
- REQUIRE(row.get_double(4) == 3.3);
- REQUIRE(row.get_string(5) == "hello");
- REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
- REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
- REQUIRE(row.get_subtable(9)->size() == 2);
- REQUIRE(row.get_subtable(10)->size() == 2);
- REQUIRE(row.get_subtable(11)->size() == 2);
- REQUIRE(row.get_subtable(12)->size() == 2);
- REQUIRE(row.get_subtable(13)->size() == 3);
- REQUIRE(row.get_subtable(14)->size() == 3);
- REQUIRE(row.get_subtable(15)->size() == 0);
- REQUIRE(row.get_linklist(16)->size() == 1);
- }
- SECTION("create can use defaults for primary key") {
- d.defaults["all types"] = {
- {"pk", INT64_C(10)},
- };
- auto obj = create(AnyDict{
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(10)}}},
- {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
- }, false);
- auto row = obj.row();
- REQUIRE(row.get_int(0) == 10);
- }
- SECTION("create does not complain about missing values for nullable fields") {
- r->begin_transaction();
- realm::Object obj;
- REQUIRE_NOTHROW(obj = Object::create(d, r, *r->schema().find("all optional types"), util::Any(AnyDict{}), false));
- r->commit_transaction();
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "pk").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "bool").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "int").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "float").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "double").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "string").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "data").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "date").has_value());
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 0);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).size() == 0);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).size() == 0);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).size() == 0);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).size() == 0);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "data array")).size() == 0);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0);
- }
- SECTION("create throws for missing values if there is no default") {
- REQUIRE_THROWS(create(AnyDict{
- {"pk", INT64_C(1)},
- {"float", 6.6f},
- }, false));
- }
- SECTION("create always sets the PK first") {
- AnyDict value{
- {"array 1", AnyVector{AnyDict{{"value", INT64_C(1)}}}},
- {"array 2", AnyVector{AnyDict{{"value", INT64_C(2)}}}},
- {"int 1", INT64_C(0)},
- {"int 2", INT64_C(0)},
- {"pk", INT64_C(7)},
- };
- // Core will throw if the list is populated before the PK is set
- r->begin_transaction();
- REQUIRE_NOTHROW(Object::create(d, r, *r->schema().find("pk after list"), util::Any(value), false));
- }
- SECTION("create with update") {
- CollectionChangeSet change;
- bool callback_called;
- Object obj = create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(10)}}},
- {"bool array", AnyVec{true, false}},
- {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
- {"float array", AnyVec{1.1f, 2.2f}},
- {"double array", AnyVec{3.3, 4.4}},
- {"string array", AnyVec{"a"s, "b"s, "c"s}},
- {"data array", AnyVec{"d"s, "e"s, "f"s}},
- {"date array", AnyVec{}},
- {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
- }, false);
- auto token = obj.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
- change = c;
- callback_called = true;
- });
- advance_and_notify(*r);
- create(AnyDict{
- {"pk", INT64_C(1)},
- {"int", INT64_C(6)},
- {"string", "a"s},
- }, true);
- callback_called = false;
- advance_and_notify(*r);
- REQUIRE(callback_called);
- REQUIRE_INDICES(change.modifications, 0);
- auto row = obj.row();
- REQUIRE(row.get_int(0) == 1);
- REQUIRE(row.get_bool(1) == true);
- REQUIRE(row.get_int(2) == 6);
- REQUIRE(row.get_float(3) == 2.2f);
- REQUIRE(row.get_double(4) == 3.3);
- REQUIRE(row.get_string(5) == "a");
- REQUIRE(row.get_binary(6) == BinaryData("olleh", 5));
- REQUIRE(row.get_timestamp(7) == Timestamp(10, 20));
- }
- SECTION("create with update - only with diffs") {
- CollectionChangeSet change;
- bool callback_called;
- AnyDict adam {
- {"name", "Adam"s},
- {"age", INT64_C(32)},
- {"scores", AnyVec{INT64_C(1), INT64_C(2)}},
- };
- AnyDict brian {
- {"name", "Brian"s},
- {"age", INT64_C(33)},
- };
- AnyDict charley {
- {"name", "Charley"s},
- {"age", INT64_C(34)},
- {"team", AnyVec{adam, brian}}
- };
- AnyDict donald {
- {"name", "Donald"s},
- {"age", INT64_C(35)},
- };
- AnyDict eddie {
- {"name", "Eddie"s},
- {"age", INT64_C(36)},
- {"assistant", donald},
- {"team", AnyVec{donald, charley}}
- };
- Object obj = create_company(eddie, true);
- auto table = r->read_group().get_table("class_person");
- REQUIRE(table->size() == 5);
- Results result(r, *table);
- auto token = result.add_notification_callback([&](CollectionChangeSet c, std::exception_ptr) {
- change = c;
- callback_called = true;
- });
- advance_and_notify(*r);
- // First update unconditionally
- create_company(eddie, true, false);
- callback_called = false;
- advance_and_notify(*r);
- REQUIRE(callback_called);
- REQUIRE_INDICES(change.modifications, 0, 1, 2, 3, 4);
- // Now, only update where differences (there should not be any diffs - so no update)
- create_company(eddie, true, true);
- REQUIRE(table->size() == 5);
- callback_called = false;
- advance_and_notify(*r);
- REQUIRE(!callback_called);
- // Now, only update sub-object)
- donald["scores"] = AnyVec{INT64_C(3), INT64_C(4), INT64_C(5)};
- // Insert the new donald
- eddie["assistant"] = donald;
- create_company(eddie, true, true);
- REQUIRE(table->size() == 5);
- callback_called = false;
- advance_and_notify(*r);
- REQUIRE(callback_called);
- REQUIRE_INDICES(change.modifications, 1);
- // Shorten list
- donald["scores"] = AnyVec{INT64_C(3), INT64_C(4)};
- eddie["assistant"] = donald;
- create_company(eddie, true, true);
- REQUIRE(table->size() == 5);
- callback_called = false;
- advance_and_notify(*r);
- REQUIRE(callback_called);
- REQUIRE_INDICES(change.modifications, 1);
- }
- SECTION("create with update - identical sub-object") {
- bool callback_called;
- bool sub_callback_called;
- Object sub_obj = create_sub(AnyDict{{"value", INT64_C(10)}}, false);
- Object obj = create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", sub_obj},
- }, false);
- auto token1 = obj.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- callback_called = true;
- });
- auto token2 = sub_obj.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- sub_callback_called = true;
- });
- advance_and_notify(*r);
- auto table = r->read_group().get_table("class_link target");
- REQUIRE(table->size() == 1);
- create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(10)}}},
- }, true, true);
- REQUIRE(table->size() == 1);
- callback_called = false;
- sub_callback_called = false;
- advance_and_notify(*r);
- REQUIRE(!callback_called);
- REQUIRE(!sub_callback_called);
- // Now change sub object
- create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(11)}}},
- }, true, true);
- callback_called = false;
- sub_callback_called = false;
- advance_and_notify(*r);
- REQUIRE(!callback_called);
- REQUIRE(sub_callback_called);
- }
- SECTION("create with update - identical array of sub-objects") {
- bool callback_called;
- auto dict = AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object array", AnyVec{ AnyDict{{"value", INT64_C(20)}}, AnyDict{{"value", INT64_C(21)}} } },
- };
- Object obj = create(dict, false);
- auto token1 = obj.add_notification_callback([&](CollectionChangeSet, std::exception_ptr) {
- callback_called = true;
- });
- advance_and_notify(*r);
- create(dict, true, true);
- callback_called = false;
- advance_and_notify(*r);
- REQUIRE(!callback_called);
- // Now change list
- dict["object array"] = AnyVec{AnyDict{{"value", INT64_C(23)}}};
- create(dict, true, true);
- callback_called = false;
- advance_and_notify(*r);
- REQUIRE(callback_called);
- }
- for (auto diffed_update : {false, true}) {
- SECTION("set existing fields to null with update "s + (diffed_update ? "(diffed)" : "(all)")) {
- AnyDict initial_values{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"bool array", AnyVec{true, false}},
- {"int array", AnyVec{INT64_C(5), INT64_C(6)}},
- {"float array", AnyVec{1.1f, 2.2f}},
- {"double array", AnyVec{3.3, 4.4}},
- {"string array", AnyVec{"a"s, "b"s, "c"s}},
- {"data array", AnyVec{"d"s, "e"s, "f"s}},
- {"date array", AnyVec{}},
- {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
- };
- r->begin_transaction();
- auto obj = Object::create(d, r, *r->schema().find("all optional types"), util::Any(initial_values));
- // Missing fields in dictionary do not update anything
- Object::create(d, r, *r->schema().find("all optional types"),
- util::Any(AnyDict{{"pk", INT64_C(1)}}), true, diffed_update);
- REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true);
- REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5);
- REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 2.2f);
- REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 3.3);
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "hello");
- REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(10, 20));
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == true);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == 5);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == 1.1f);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == 3.3);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == "a");
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0);
- // Set all properties to null
- AnyDict null_values{
- {"pk", INT64_C(1)},
- {"bool", util::Any()},
- {"int", util::Any()},
- {"float", util::Any()},
- {"double", util::Any()},
- {"string", util::Any()},
- {"data", util::Any()},
- {"date", util::Any()},
- {"bool array", AnyVec{util::Any()}},
- {"int array", AnyVec{util::Any()}},
- {"float array", AnyVec{util::Any()}},
- {"double array", AnyVec{util::Any()}},
- {"string array", AnyVec{util::Any()}},
- {"data array", AnyVec{util::Any()}},
- {"date array", AnyVec{Timestamp()}},
- };
- Object::create(d, r, *r->schema().find("all optional types"), util::Any(null_values), true, diffed_update);
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "bool").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "int").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "float").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "double").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "string").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "data").has_value());
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "date").has_value());
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == util::none);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == util::none);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == util::none);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == util::none);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == StringData());
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "data array")).get<BinaryData>(0) == BinaryData());
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).get<Timestamp>(0) == Timestamp());
- // Set all properties back to non-null
- Object::create(d, r, *r->schema().find("all optional types"), util::Any(initial_values), true, diffed_update);
- REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true);
- REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5);
- REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 2.2f);
- REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 3.3);
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "hello");
- REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(10, 20));
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).get<util::Optional<bool>>(0) == true);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "int array")).get<util::Optional<int64_t>>(0) == 5);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "float array")).get<util::Optional<float>>(0) == 1.1f);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "double array")).get<util::Optional<double>>(0) == 3.3);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "string array")).get<StringData>(0) == "a");
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "date array")).size() == 0);
- }
- }
- SECTION("create throws for duplicate pk if update is not specified") {
- create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(10)}}},
- {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
- }, false);
- REQUIRE_THROWS(create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"object", AnyDict{{"value", INT64_C(10)}}},
- {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
- }, false));
- }
- SECTION("create with explicit null pk does not fall back to default") {
- d.defaults["nullable int pk"] = {
- {"pk", INT64_C(10)},
- };
- d.defaults["nullable string pk"] = {
- {"pk", "value"s},
- };
- auto create = [&](util::Any&& value, StringData type) {
- r->begin_transaction();
- auto obj = Object::create(d, r, *r->schema().find(type), value, false);
- r->commit_transaction();
- return obj;
- };
- auto obj = create(AnyDict{{"pk", d.null_value()}}, "nullable int pk");
- REQUIRE(obj.row().is_null(0));
- obj = create(AnyDict{{"pk", d.null_value()}}, "nullable string pk");
- REQUIRE(obj.row().is_null(0));
- obj = create(AnyDict{{}}, "nullable int pk");
- REQUIRE(obj.row().get_int(0) == 10);
- obj = create(AnyDict{{}}, "nullable string pk");
- REQUIRE(obj.row().get_string(0) == "value");
- }
- SECTION("getters and setters") {
- r->begin_transaction();
- auto& table = *r->read_group().get_table("class_all types");
- table.add_empty_row();
- Object obj(r, *r->schema().find("all types"), table[0]);
- auto& link_table = *r->read_group().get_table("class_link target");
- link_table.add_empty_row();
- Object linkobj(r, *r->schema().find("link target"), link_table[0]);
- obj.set_property_value(d, "bool", util::Any(true), false);
- REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(d, "bool")) == true);
- obj.set_property_value(d, "int", util::Any(INT64_C(5)), false);
- REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(d, "int")) == 5);
- obj.set_property_value(d, "float", util::Any(1.23f), false);
- REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(d, "float")) == 1.23f);
- obj.set_property_value(d, "double", util::Any(1.23), false);
- REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(d, "double")) == 1.23);
- obj.set_property_value(d, "string", util::Any("abc"s), false);
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "string")) == "abc");
- obj.set_property_value(d, "data", util::Any("abc"s), false);
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(d, "data")) == "abc");
- obj.set_property_value(d, "date", util::Any(Timestamp(1, 2)), false);
- REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(d, "date")) == Timestamp(1, 2));
- REQUIRE_FALSE(obj.get_property_value<util::Any>(d, "object").has_value());
- obj.set_property_value(d, "object", util::Any(linkobj), false);
- REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(d, "object")).row().get_index() == linkobj.row().get_index());
- auto linking = any_cast<Results>(linkobj.get_property_value<util::Any>(d, "origin"));
- REQUIRE(linking.size() == 1);
- REQUIRE_THROWS(obj.set_property_value(d, "pk", util::Any(INT64_C(5)), false));
- REQUIRE_THROWS(obj.set_property_value(d, "not a property", util::Any(INT64_C(5)), false));
- r->commit_transaction();
- REQUIRE_THROWS(obj.get_property_value<util::Any>(d, "not a property"));
- REQUIRE_THROWS(obj.set_property_value(d, "int", util::Any(INT64_C(5)), false));
- }
- SECTION("list property self-assign is a no-op") {
- auto obj = create(AnyDict{
- {"pk", INT64_C(1)},
- {"bool", true},
- {"int", INT64_C(5)},
- {"float", 2.2f},
- {"double", 3.3},
- {"string", "hello"s},
- {"data", "olleh"s},
- {"date", Timestamp(10, 20)},
- {"bool array", AnyVec{true, false}},
- {"object array", AnyVec{AnyDict{{"value", INT64_C(20)}}}},
- }, false);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 2);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "object array")).size() == 1);
- r->begin_transaction();
- obj.set_property_value(d, "bool array", obj.get_property_value<util::Any>(d, "bool array"), false);
- obj.set_property_value(d, "object array", obj.get_property_value<util::Any>(d, "object array"), false);
- r->commit_transaction();
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "bool array")).size() == 2);
- REQUIRE(any_cast<List&&>(obj.get_property_value<util::Any>(d, "object array")).size() == 1);
- }
- #if REALM_ENABLE_SYNC
- if (!util::EventLoop::has_implementation())
- return;
- SyncServer server(false);
- SyncTestFile config1(server, "shared");
- config1.schema = config.schema;
- SyncTestFile config2(server, "shared");
- config2.schema = config.schema;
- SECTION("defaults do not override values explicitly passed to create()") {
- AnyDict v1{
- {"pk", INT64_C(7)},
- {"array 1", AnyVector{AnyDict{{"value", INT64_C(1)}}}},
- {"array 2", AnyVector{AnyDict{{"value", INT64_C(2)}}}},
- };
- auto v2 = v1;
- v1["int 1"] = INT64_C(1);
- v2["int 2"] = INT64_C(2);
- auto r1 = Realm::get_shared_realm(config1);
- auto r2 = Realm::get_shared_realm(config2);
- TestContext c1(r1);
- TestContext c2(r2);
- c1.defaults["pk after list"] = {
- {"int 1", INT64_C(10)},
- {"int 2", INT64_C(10)},
- };
- c2.defaults = c1.defaults;
- r1->begin_transaction();
- r2->begin_transaction();
- auto obj = Object::create(c1, r1, *r1->schema().find("pk after list"), util::Any(v1), false);
- Object::create(c2, r2, *r2->schema().find("pk after list"), util::Any(v2), false);
- r2->commit_transaction();
- r1->commit_transaction();
- server.start();
- util::EventLoop::main().run_until([&] {
- return r1->read_group().get_table("class_array target")->size() == 4;
- });
- // With stable IDs, sync creates the primary key column at index 0.
- REQUIRE(obj.row().get_int(0) == 7); // pk
- REQUIRE(obj.row().get_linklist(1)->size() == 2);
- REQUIRE(obj.row().get_int(2) == 1); // non-default from r1
- REQUIRE(obj.row().get_int(3) == 2); // non-default from r2
- REQUIRE(obj.row().get_linklist(4)->size() == 2);
- }
- #endif
- }
|