1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2016 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 "catch2/catch.hpp"
- #include "util/test_file.hpp"
- #include "object_schema.hpp"
- #include "object_store.hpp"
- #include "property.hpp"
- #include "schema.hpp"
- #include "impl/object_accessor_impl.hpp"
- #include <realm/descriptor.hpp>
- #include <realm/group.hpp>
- #include <realm/table.hpp>
- #include <realm/util/scope_exit.hpp>
- #ifdef _WIN32
- #include <Windows.h>
- #endif
- using namespace realm;
- #define VERIFY_SCHEMA(r) verify_schema((r), __LINE__)
- #define REQUIRE_UPDATE_SUCCEEDS(r, s, version) do { \
- REQUIRE_NOTHROW((r).update_schema(s, version)); \
- VERIFY_SCHEMA(r); \
- REQUIRE((r).schema() == s); \
- } while (0)
- #define REQUIRE_NO_MIGRATION_NEEDED(r, schema1, schema2) do { \
- REQUIRE_UPDATE_SUCCEEDS(r, schema1, 0); \
- REQUIRE_UPDATE_SUCCEEDS(r, schema2, 0); \
- } while (0)
- #define REQUIRE_MIGRATION_NEEDED(r, schema1, schema2) do { \
- REQUIRE_UPDATE_SUCCEEDS(r, schema1, 0); \
- REQUIRE_THROWS((r).update_schema(schema2)); \
- REQUIRE((r).schema() == schema1); \
- REQUIRE_UPDATE_SUCCEEDS(r, schema2, 1); \
- } while (0)
- namespace {
- void verify_schema(Realm& r, int line)
- {
- CAPTURE(line);
- for (auto&& object_schema : r.schema()) {
- auto table = ObjectStore::table_for_object_type(r.read_group(), object_schema.name);
- REQUIRE(table);
- CAPTURE(object_schema.name);
- std::string primary_key = ObjectStore::get_primary_key_for_object(r.read_group(), object_schema.name);
- REQUIRE(primary_key == object_schema.primary_key);
- for (auto&& prop : object_schema.persisted_properties) {
- size_t col = table->get_column_index(prop.name);
- CAPTURE(prop.name);
- REQUIRE(col != npos);
- REQUIRE(col == prop.table_column);
- REQUIRE(to_underlying(ObjectSchema::from_core_type(*table->get_descriptor(), col)) ==
- to_underlying(prop.type));
- REQUIRE(table->has_search_index(col) == prop.requires_index());
- REQUIRE(bool(prop.is_primary) == (prop.name == primary_key));
- }
- }
- }
- TableRef get_table(std::shared_ptr<Realm> const& realm, StringData object_type)
- {
- return ObjectStore::table_for_object_type(realm->read_group(), object_type);
- }
- // Helper functions for modifying Schema objects, mostly for the sake of making
- // it clear what exactly is different about the 2+ schema objects used in
- // various tests
- Schema add_table(Schema const& schema, ObjectSchema object_schema)
- {
- std::vector<ObjectSchema> new_schema(schema.begin(), schema.end());
- new_schema.push_back(std::move(object_schema));
- return new_schema;
- }
- Schema remove_table(Schema const& schema, StringData object_name)
- {
- std::vector<ObjectSchema> new_schema;
- std::remove_copy_if(schema.begin(), schema.end(), std::back_inserter(new_schema),
- [&](auto&& object_schema) { return object_schema.name == object_name; });
- return new_schema;
- }
- Schema add_property(Schema schema, StringData object_name, Property property)
- {
- schema.find(object_name)->persisted_properties.push_back(std::move(property));
- return schema;
- }
- Schema remove_property(Schema schema, StringData object_name, StringData property_name)
- {
- auto& properties = schema.find(object_name)->persisted_properties;
- properties.erase(find_if(begin(properties), end(properties),
- [&](auto&& prop) { return prop.name == property_name; }));
- return schema;
- }
- Schema set_indexed(Schema schema, StringData object_name, StringData property_name, bool value)
- {
- schema.find(object_name)->property_for_name(property_name)->is_indexed = value;
- return schema;
- }
- Schema set_optional(Schema schema, StringData object_name, StringData property_name, bool value)
- {
- auto& prop = *schema.find(object_name)->property_for_name(property_name);
- if (value)
- prop.type |= PropertyType::Nullable;
- else
- prop.type &= ~PropertyType::Nullable;
- return schema;
- }
- Schema set_type(Schema schema, StringData object_name, StringData property_name, PropertyType value)
- {
- schema.find(object_name)->property_for_name(property_name)->type = value;
- return schema;
- }
- Schema set_target(Schema schema, StringData object_name, StringData property_name, StringData new_target)
- {
- schema.find(object_name)->property_for_name(property_name)->object_type = new_target;
- return schema;
- }
- Schema set_primary_key(Schema schema, StringData object_name, StringData new_primary_property)
- {
- auto& object_schema = *schema.find(object_name);
- if (auto old_primary = object_schema.primary_key_property()) {
- old_primary->is_primary = false;
- }
- if (new_primary_property.size()) {
- object_schema.property_for_name(new_primary_property)->is_primary = true;
- }
- object_schema.primary_key = new_primary_property;
- return schema;
- }
- } // anonymous namespace
- TEST_CASE("migration: Automatic") {
- InMemoryTestFile config;
- config.automatic_change_notifications = false;
- SECTION("no migration required") {
- SECTION("add object schema") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema1 = {};
- Schema schema2 = add_table(schema1, {"object", {
- {"value", PropertyType::Int}
- }});
- Schema schema3 = add_table(schema2, {"object2", {
- {"value", PropertyType::Int}
- }});
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 0);
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema3, 0);
- }
- SECTION("remove object schema") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema1 = {
- {"object", {
- {"value", PropertyType::Int}
- }},
- {"object2", {
- {"value", PropertyType::Int}
- }},
- };
- Schema schema2 = remove_table(schema1, "object2");
- Schema schema3 = remove_table(schema2, "object");
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema3, 0);
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 0);
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
- }
- SECTION("add index") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int}
- }},
- };
- REQUIRE_NO_MIGRATION_NEEDED(*realm, schema, set_indexed(schema, "object", "value", true));
- }
- SECTION("remove index") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}
- }},
- };
- REQUIRE_NO_MIGRATION_NEEDED(*realm, schema, set_indexed(schema, "object", "value", false));
- }
- SECTION("reordering properties") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema1 = {
- {"object", {
- {"col1", PropertyType::Int},
- {"col2", PropertyType::Int},
- }},
- };
- Schema schema2 = {
- {"object", {
- {"col2", PropertyType::Int},
- {"col1", PropertyType::Int},
- }},
- };
- REQUIRE_NO_MIGRATION_NEEDED(*realm, schema1, schema2);
- }
- }
- SECTION("migration required") {
- SECTION("add property to existing object schema") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema1 = {
- {"object", {
- {"col1", PropertyType::Int},
- }},
- };
- auto schema2 = add_property(schema1, "object",
- {"col2", PropertyType::Int});
- REQUIRE_MIGRATION_NEEDED(*realm, schema1, schema2);
- }
- SECTION("remove property from existing object schema") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"col1", PropertyType::Int},
- {"col2", PropertyType::Int},
- }},
- };
- REQUIRE_MIGRATION_NEEDED(*realm, schema, remove_property(schema, "object", "col2"));
- }
- SECTION("migratation which replaces a persisted property with a computed one") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema1 = {
- {"object", {
- {"value", PropertyType::Int},
- {"link", PropertyType::Object|PropertyType::Nullable, "object2"},
- }},
- {"object2", {
- {"value", PropertyType::Int},
- {"inverse", PropertyType::Object|PropertyType::Nullable, "object"},
- }},
- };
- Schema schema2 = remove_property(schema1, "object", "link");
- Property new_property{"link", PropertyType::LinkingObjects|PropertyType::Array, "object2", "inverse"};
- schema2.find("object")->computed_properties.emplace_back(new_property);
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
- REQUIRE_THROWS((*realm).update_schema(schema2));
- REQUIRE((*realm).schema() == schema1);
- REQUIRE_NOTHROW((*realm).update_schema(schema2, 1,
- [](SharedRealm, SharedRealm, Schema&) { /* empty but present migration handler */ }));
- VERIFY_SCHEMA(*realm);
- REQUIRE((*realm).schema() == schema2);
- }
- SECTION("change property type") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_MIGRATION_NEEDED(*realm, schema, set_type(schema, "object", "value", PropertyType::Float));
- }
- SECTION("make property nullable") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", true));
- }
- SECTION("make property required") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int|PropertyType::Nullable},
- }},
- };
- REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", false));
- }
- SECTION("change link target") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"target 1", {
- {"value", PropertyType::Int},
- }},
- {"target 2", {
- {"value", PropertyType::Int},
- }},
- {"origin", {
- {"value", PropertyType::Object|PropertyType::Nullable, "target 1"},
- }},
- };
- REQUIRE_MIGRATION_NEEDED(*realm, schema, set_target(schema, "origin", "value", "target 2"));
- }
- SECTION("add pk") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", "value"));
- }
- SECTION("remove pk") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int, Property::IsPrimary{true}},
- }},
- };
- REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", ""));
- }
- SECTION("adding column and table in same migration doesn't add duplicate columns") {
- auto realm = Realm::get_shared_realm(config);
- Schema schema1 = {
- {"object", {
- {"col1", PropertyType::Int},
- }},
- };
- auto schema2 = add_table(add_property(schema1, "object", {"col2", PropertyType::Int}),
- {"object2", {{"value", PropertyType::Int}}});
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
- REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 1);
- auto& table = *get_table(realm, "object2");
- REQUIRE(table.get_column_count() == 1);
- }
- }
- SECTION("migration block invocations") {
- SECTION("not called for initial creation of schema") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 5, [](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); });
- }
- SECTION("not called when schema version is unchanged even if there are schema changes") {
- Schema schema1 = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- Schema schema2 = add_table(schema1, {"second object", {
- {"value", PropertyType::Int},
- }});
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema1, 1);
- realm->update_schema(schema2, 1, [](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); });
- }
- SECTION("called when schema version is bumped even if there are no schema changes") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema);
- bool called = false;
- realm->update_schema(schema, 5, [&](SharedRealm, SharedRealm, Schema&) { called = true; });
- REQUIRE(called);
- }
- }
- SECTION("migration errors") {
- SECTION("schema version cannot go down") {
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema({}, 1);
- realm->update_schema({}, 2);
- REQUIRE_THROWS(realm->update_schema({}, 0));
- }
- SECTION("insert duplicate keys for existing PK during migration") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int, Property::IsPrimary{true}},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- REQUIRE_THROWS(realm->update_schema(schema, 2, [](SharedRealm, SharedRealm realm, Schema&) {
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- table->add_empty_row(2);
- }));
- }
- SECTION("add pk to existing table with duplicate keys") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- table->add_empty_row(2);
- schema = set_primary_key(schema, "object", "value");
- REQUIRE_THROWS(realm->update_schema(schema, 2, nullptr));
- }
- SECTION("throwing an exception from migration function rolls back all changes") {
- Schema schema1 = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- Schema schema2 = add_property(schema1, "object",
- {"value2", PropertyType::Int});
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema1, 1);
- REQUIRE_THROWS(realm->update_schema(schema2, 2, [](SharedRealm, SharedRealm realm, Schema&) {
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- table->add_empty_row();
- throw 5;
- }));
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- REQUIRE(table->size() == 0);
- REQUIRE(realm->schema_version() == 1);
- REQUIRE(realm->schema() == schema1);
- }
- }
- SECTION("valid migrations") {
- SECTION("changing all columns does not lose row count") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- realm->begin_transaction();
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- table->add_empty_row(10);
- realm->commit_transaction();
- schema = set_type(schema, "object", "value", PropertyType::Float);
- realm->update_schema(schema, 2);
- REQUIRE(table->size() == 10);
- }
- SECTION("values for required properties are copied when converitng to nullable") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- realm->begin_transaction();
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- table->add_empty_row(10);
- for (size_t i = 0; i < 10; ++i)
- table->set_int(0, i, i);
- realm->commit_transaction();
- realm->update_schema(set_optional(schema, "object", "value", true), 2);
- for (int i = 0; i < 10; ++i)
- REQUIRE(table->get_int(0, i) == i);
- }
- SECTION("values for nullable properties are discarded when converitng to required") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int|PropertyType::Nullable},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- realm->begin_transaction();
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- table->add_empty_row(10);
- for (size_t i = 0; i < 10; ++i)
- table->set_int(0, i, i);
- realm->commit_transaction();
- realm->update_schema(set_optional(schema, "object", "value", false), 2);
- for (size_t i = 0; i < 10; ++i)
- REQUIRE(table->get_int(0, i) == 0);
- }
- SECTION("deleting table removed from the schema deletes it") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int|PropertyType::Nullable},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- realm->update_schema({}, 2, [](SharedRealm, SharedRealm realm, Schema&) {
- ObjectStore::delete_data_for_object(realm->read_group(), "object");
- });
- REQUIRE_FALSE(ObjectStore::table_for_object_type(realm->read_group(), "object"));
- }
- SECTION("deleting table still in the schema recreates it with no rows") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int|PropertyType::Nullable},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- realm->begin_transaction();
- ObjectStore::table_for_object_type(realm->read_group(), "object")->add_empty_row();
- realm->commit_transaction();
- realm->update_schema(schema, 2, [](SharedRealm, SharedRealm realm, Schema&) {
- ObjectStore::delete_data_for_object(realm->read_group(), "object");
- });
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- REQUIRE(table);
- REQUIRE(table->size() == 0);
- }
- SECTION("deleting table which doesn't exist does nothing") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int|PropertyType::Nullable},
- }},
- };
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- REQUIRE_NOTHROW(realm->update_schema({}, 2, [](SharedRealm, SharedRealm realm, Schema&) {
- ObjectStore::delete_data_for_object(realm->read_group(), "foo");
- }));
- }
- SECTION("subtables columns are not modified by unrelated changes") {
- config.in_memory = false;
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- {
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema, 1);
- realm->begin_transaction();
- get_table(realm, "object")->add_column(type_Table, "subtable");
- realm->commit_transaction();
- }
- // close and reopen the Realm to ensure it rereads the schema from
- // the group
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(add_property(schema, "object", {"value 2", PropertyType::Int}), 2);
- auto& table = *get_table(realm, "object");
- REQUIRE(table.get_column_type(1) == type_Table);
- REQUIRE(table.get_column_count() == 3);
- }
- }
- SECTION("schema correctness during migration") {
- InMemoryTestFile config;
- config.schema_mode = SchemaMode::Automatic;
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
- {"optional", PropertyType::Int|PropertyType::Nullable},
- }},
- {"link origin", {
- {"not a pk", PropertyType::Int},
- {"object", PropertyType::Object|PropertyType::Nullable, "object"},
- {"array", PropertyType::Array|PropertyType::Object, "object"},
- }}
- };
- realm->update_schema(schema);
- #define VERIFY_SCHEMA_IN_MIGRATION(target_schema) do { \
- Schema new_schema = (target_schema); \
- realm->update_schema(new_schema, 1, [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) { \
- REQUIRE(old_realm->schema_version() == 0); \
- REQUIRE(old_realm->schema() == schema); \
- REQUIRE(new_realm->schema_version() == 1); \
- REQUIRE(new_realm->schema() == new_schema); \
- VERIFY_SCHEMA(*old_realm); \
- VERIFY_SCHEMA(*new_realm); \
- }); \
- REQUIRE(realm->schema() == new_schema); \
- VERIFY_SCHEMA(*realm); \
- } while (false)
- SECTION("add new table") {
- VERIFY_SCHEMA_IN_MIGRATION(add_table(schema, {"new table", {
- {"value", PropertyType::Int},
- }}));
- }
- SECTION("add property to table") {
- VERIFY_SCHEMA_IN_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}));
- }
- SECTION("remove property from table") {
- VERIFY_SCHEMA_IN_MIGRATION(remove_property(schema, "object", "value"));
- }
- SECTION("remove multiple properties from table") {
- VERIFY_SCHEMA_IN_MIGRATION(remove_property(remove_property(schema, "object", "value"), "object", "optional"));
- }
- SECTION("add primary key to table") {
- VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "link origin", "not a pk"));
- }
- SECTION("remove primary key from table") {
- VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "object", ""));
- }
- SECTION("change primary key") {
- VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "object", "value"));
- }
- SECTION("change property type") {
- VERIFY_SCHEMA_IN_MIGRATION(set_type(schema, "object", "value", PropertyType::Date));
- }
- SECTION("change link target") {
- VERIFY_SCHEMA_IN_MIGRATION(set_target(schema, "link origin", "object", "link origin"));
- }
- SECTION("change linklist target") {
- VERIFY_SCHEMA_IN_MIGRATION(set_target(schema, "link origin", "array", "link origin"));
- }
- SECTION("make property optional") {
- VERIFY_SCHEMA_IN_MIGRATION(set_optional(schema, "object", "value", true));
- }
- SECTION("make property required") {
- VERIFY_SCHEMA_IN_MIGRATION(set_optional(schema, "object", "optional", false));
- }
- SECTION("add index") {
- VERIFY_SCHEMA_IN_MIGRATION(set_indexed(schema, "object", "optional", true));
- }
- SECTION("remove index") {
- VERIFY_SCHEMA_IN_MIGRATION(set_indexed(schema, "object", "value", false));
- }
- SECTION("reorder properties") {
- auto schema2 = schema;
- auto& properties = schema2.find("object")->persisted_properties;
- std::swap(properties[0], properties[1]);
- VERIFY_SCHEMA_IN_MIGRATION(schema2);
- }
- }
- SECTION("object accessors inside migrations") {
- using namespace std::string_literals;
- Schema schema{
- {"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"},
- {"array", PropertyType::Object|PropertyType::Array, "array target"},
- }},
- {"link target", {
- {"value", PropertyType::Int},
- }, {
- {"origin", PropertyType::LinkingObjects|PropertyType::Array, "all types", "object"},
- }},
- {"array target", {
- {"value", PropertyType::Int},
- }},
- };
- InMemoryTestFile config;
- config.schema_mode = SchemaMode::Automatic;
- config.schema = schema;
- auto realm = Realm::get_shared_realm(config);
- CppContext ctx(realm);
- util::Any values = 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)}}}},
- };
- realm->begin_transaction();
- Object::create(ctx, realm, *realm->schema().find("all types"), values);
- realm->commit_transaction();
- SECTION("read values from old realm") {
- Schema schema{
- {"all types", {
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- }},
- };
- realm->update_schema(schema, 2, [](auto old_realm, auto new_realm, Schema&) {
- CppContext ctx(old_realm);
- Object obj = Object::get_for_primary_key(ctx, old_realm, "all types",
- util::Any(INT64_C(1)));
- REQUIRE(obj.is_valid());
- REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true);
- REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5);
- REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f);
- REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3);
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello");
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh");
- REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20));
- auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object"));
- REQUIRE(link.is_valid());
- REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10);
- auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array"));
- REQUIRE(list.size() == 1);
- CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array"));
- link = any_cast<Object>(list.get(list_ctx, 0));
- REQUIRE(link.is_valid());
- REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20);
- CppContext ctx2(new_realm);
- obj = Object::get_for_primary_key(ctx, new_realm, "all types",
- util::Any(INT64_C(1)));
- REQUIRE(obj.is_valid());
- REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool"));
- });
- }
- SECTION("cannot mutate old realm") {
- realm->update_schema(schema, 2, [](auto old_realm, auto, Schema&) {
- CppContext ctx(old_realm);
- Object obj = Object::get_for_primary_key(ctx, old_realm, "all types",
- util::Any(INT64_C(1)));
- REQUIRE(obj.is_valid());
- REQUIRE_THROWS(obj.set_property_value(ctx, "bool", util::Any(false)));
- REQUIRE_THROWS(old_realm->begin_transaction());
- });
- }
- SECTION("cannot read values for removed properties from new realm") {
- Schema schema{
- {"all types", {
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- }},
- };
- realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
- CppContext ctx(new_realm);
- Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
- util::Any(INT64_C(1)));
- REQUIRE(obj.is_valid());
- REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool"));
- REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "object"));
- REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "array"));
- });
- }
- SECTION("read values from new object") {
- realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
- CppContext ctx(new_realm);
- Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
- util::Any(INT64_C(1)));
- REQUIRE(obj.is_valid());
- auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object"));
- REQUIRE(link.is_valid());
- REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10);
- auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array"));
- REQUIRE(list.size() == 1);
- CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array"));
- link = any_cast<Object>(list.get(list_ctx, 0));
- REQUIRE(link.is_valid());
- REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20);
- });
- }
- SECTION("read and write values in new object") {
- realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
- CppContext ctx(new_realm);
- Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
- util::Any(INT64_C(1)));
- REQUIRE(obj.is_valid());
- REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true);
- obj.set_property_value(ctx, "bool", util::Any(false));
- REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false);
- REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5);
- obj.set_property_value(ctx, "int", util::Any(INT64_C(6)));
- REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 6);
- REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f);
- obj.set_property_value(ctx, "float", util::Any(1.23f));
- REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 1.23f);
- REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3);
- obj.set_property_value(ctx, "double", util::Any(1.23));
- REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 1.23);
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello");
- obj.set_property_value(ctx, "string", util::Any("abc"s));
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "abc");
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh");
- obj.set_property_value(ctx, "data", util::Any("abc"s));
- REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "abc");
- REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20));
- obj.set_property_value(ctx, "date", util::Any(Timestamp(1, 2)));
- REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(1, 2));
- Object linked_obj(new_realm, "link target", 0);
- Object new_obj(new_realm, "link target", get_table(new_realm, "link target")->add_empty_row());
- auto linking = any_cast<Results>(linked_obj.get_property_value<util::Any>(ctx, "origin"));
- REQUIRE(linking.size() == 1);
- REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).row().get_index()
- == linked_obj.row().get_index());
- obj.set_property_value(ctx, "object", util::Any(new_obj));
- REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).row().get_index()
- == new_obj.row().get_index());
- REQUIRE(linking.size() == 0);
- });
- }
- SECTION("create object in new realm") {
- realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) {
- REQUIRE(new_realm->is_in_transaction());
- CppContext ctx(new_realm);
- any_cast<AnyDict&>(values)["pk"] = INT64_C(2);
- Object obj = Object::create(ctx, new_realm, "all types", values);
- REQUIRE(get_table(new_realm, "all types")->size() == 2);
- REQUIRE(get_table(new_realm, "link target")->size() == 2);
- REQUIRE(get_table(new_realm, "array target")->size() == 2);
- REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "pk")) == 2);
- });
- }
- SECTION("upsert in new realm") {
- realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) {
- REQUIRE(new_realm->is_in_transaction());
- CppContext ctx(new_realm);
- any_cast<AnyDict&>(values)["bool"] = false;
- Object obj = Object::create(ctx, new_realm, "all types", values, CreatePolicy::UpdateAll);
- REQUIRE(get_table(new_realm, "all types")->size() == 1);
- REQUIRE(get_table(new_realm, "link target")->size() == 2);
- REQUIRE(get_table(new_realm, "array target")->size() == 2);
- REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false);
- });
- }
- SECTION("change primary key property type") {
- schema = set_type(schema, "all types", "pk", PropertyType::String);
- realm->update_schema(schema, 2, [](auto, auto new_realm, auto&) {
- Object obj(new_realm, "all types", 0);
- CppContext ctx(new_realm);
- obj.set_property_value(ctx, "pk", util::Any("1"s));
- });
- }
- SECTION("set primary key to duplicate values in migration") {
- auto bad_migration = [&](auto, auto new_realm, Schema&) {
- // shoud be able to create a new object with the same PK
- REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values));
- REQUIRE(get_table(new_realm, "all types")->size() == 2);
- // but it'll fail at the end
- };
- REQUIRE_THROWS_AS(realm->update_schema(schema, 2, bad_migration), DuplicatePrimaryKeyValueException);
- REQUIRE(get_table(realm, "all types")->size() == 1);
- auto good_migration = [&](auto, auto new_realm, Schema&) {
- REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values));
- // Change the old object's PK to elminate the duplication
- Object old_obj(new_realm, "all types", 0);
- CppContext ctx(new_realm);
- old_obj.set_property_value(ctx, "pk", util::Any(INT64_C(5)));
- };
- REQUIRE_NOTHROW(realm->update_schema(schema, 2, good_migration));
- REQUIRE(get_table(realm, "all types")->size() == 2);
- }
- }
- SECTION("property renaming") {
- InMemoryTestFile config;
- config.schema_mode = SchemaMode::Automatic;
- auto realm = Realm::get_shared_realm(config);
- struct Rename {
- StringData object_type;
- StringData old_name;
- StringData new_name;
- };
- auto apply_renames = [&](std::initializer_list<Rename> renames) -> Realm::MigrationFunction {
- return [=](SharedRealm, SharedRealm realm, Schema& schema) {
- for (auto rename : renames) {
- ObjectStore::rename_property(realm->read_group(), schema,
- rename.object_type, rename.old_name, rename.new_name);
- }
- };
- };
- #define FAILED_RENAME(old_schema, new_schema, error, ...) do { \
- realm->update_schema(old_schema, 1); \
- REQUIRE_THROWS_WITH(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__})), error); \
- } while (false)
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- SECTION("table does not exist in old schema") {
- auto schema2 = add_table(schema, {"object 2", {
- {"value 2", PropertyType::Int},
- }});
- FAILED_RENAME(schema, schema2,
- "Cannot rename property 'object 2.value' because it does not exist.",
- {"object 2", "value", "value 2"});
- }
- SECTION("table does not exist in new schema") {
- FAILED_RENAME(schema, {},
- "Cannot rename properties for type 'object' because it has been removed from the Realm.",
- {"object", "value", "value 2"});
- }
- SECTION("property does not exist in old schema") {
- auto schema2 = add_property(schema, "object", {"new", PropertyType::Int});
- FAILED_RENAME(schema, schema2,
- "Cannot rename property 'object.nonexistent' because it does not exist.",
- {"object", "nonexistent", "new"});
- }
- auto rename_value = [](Schema schema) {
- schema.find("object")->property_for_name("value")->name = "new";
- return schema;
- };
- SECTION("property does not exist in new schema") {
- FAILED_RENAME(schema, rename_value(schema),
- "Renamed property 'object.nonexistent' does not exist.",
- {"object", "value", "nonexistent"});
- }
- SECTION("source propety still exists in the new schema") {
- auto schema2 = add_property(schema, "object",
- {"new", PropertyType::Int});
- FAILED_RENAME(schema, schema2,
- "Cannot rename property 'object.value' to 'new' because the source property still exists.",
- {"object", "value", "new"});
- }
- SECTION("different type") {
- auto schema2 = rename_value(set_type(schema, "object", "value", PropertyType::Date));
- FAILED_RENAME(schema, schema2,
- "Cannot rename property 'object.value' to 'new' because it would change from type 'int' to 'date'.",
- {"object", "value", "new"});
- }
- SECTION("different link targets") {
- Schema schema = {
- {"target", {
- {"value", PropertyType::Int},
- }},
- {"origin", {
- {"link", PropertyType::Object|PropertyType::Nullable, "target"},
- }},
- };
- auto schema2 = set_target(schema, "origin", "link", "origin");
- schema2.find("origin")->property_for_name("link")->name = "new";
- FAILED_RENAME(schema, schema2,
- "Cannot rename property 'origin.link' to 'new' because it would change from type '<target>' to '<origin>'.",
- {"origin", "link", "new"});
- }
- SECTION("different linklist targets") {
- Schema schema = {
- {"target", {
- {"value", PropertyType::Int},
- }},
- {"origin", {
- {"link", PropertyType::Array|PropertyType::Object, "target"},
- }},
- };
- auto schema2 = set_target(schema, "origin", "link", "origin");
- schema2.find("origin")->property_for_name("link")->name = "new";
- FAILED_RENAME(schema, schema2,
- "Cannot rename property 'origin.link' to 'new' because it would change from type 'array<target>' to 'array<origin>'.",
- {"origin", "link", "new"});
- }
- SECTION("make required") {
- schema = set_optional(schema, "object", "value", true);
- auto schema2 = rename_value(set_optional(schema, "object", "value", false));
- FAILED_RENAME(schema, schema2,
- "Cannot rename property 'object.value' to 'new' because it would change from optional to required.",
- {"object", "value", "new"});
- }
- auto init = [&](Schema const& old_schema) {
- realm->update_schema(old_schema, 1);
- realm->begin_transaction();
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- table->add_empty_row();
- table->set_int(0, 0, 10);
- realm->commit_transaction();
- };
- #define SUCCESSFUL_RENAME(old_schema, new_schema, ...) do { \
- init(old_schema); \
- REQUIRE_NOTHROW(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__}))); \
- REQUIRE(realm->schema() == new_schema); \
- VERIFY_SCHEMA(*realm); \
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_int(0, 0) == 10); \
- } while (false)
- SECTION("basic valid rename") {
- auto schema2 = rename_value(schema);
- SUCCESSFUL_RENAME(schema, schema2,
- {"object", "value", "new"});
- }
- SECTION("chained rename") {
- auto schema2 = rename_value(schema);
- SUCCESSFUL_RENAME(schema, schema2,
- {"object", "value", "a"},
- {"object", "a", "b"},
- {"object", "b", "new"});
- }
- SECTION("old is pk, new is not") {
- auto schema2 = rename_value(schema);
- schema = set_primary_key(schema, "object", "value");
- SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
- }
- SECTION("new is pk, old is not") {
- auto schema2 = set_primary_key(rename_value(schema), "object", "new");
- SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
- }
- SECTION("both are pk") {
- schema = set_primary_key(schema, "object", "value");
- auto schema2 = set_primary_key(rename_value(schema), "object", "new");
- SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
- }
- SECTION("make optional") {
- auto schema2 = rename_value(set_optional(schema, "object", "value", true));
- SUCCESSFUL_RENAME(schema, schema2,
- {"object", "value", "new"});
- }
- SECTION("add index") {
- auto schema2 = rename_value(set_indexed(schema, "object", "value", true));
- SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
- }
- SECTION("remove index") {
- auto schema2 = rename_value(schema);
- schema = set_indexed(schema, "object", "value", true);
- SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
- }
- }
- }
- TEST_CASE("migration: Immutable") {
- TestFile config;
- auto realm_with_schema = [&](Schema schema) {
- {
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(std::move(schema));
- }
- config.schema_mode = SchemaMode::Immutable;
- return Realm::get_shared_realm(config);
- };
- SECTION("allowed schema mismatches") {
- SECTION("index") {
- auto realm = realm_with_schema({
- {"object", {
- {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
- {"unindexed", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"indexed", PropertyType::Int},
- {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- REQUIRE(realm->schema() == schema);
- for (auto& object_schema : realm->schema()) {
- for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
- REQUIRE(i == object_schema.persisted_properties[i].table_column);
- }
- }
- }
- SECTION("extra tables") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- {"object 2", {
- {"value", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- }
- SECTION("missing tables") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- {"second object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- REQUIRE(realm->schema() == schema);
- auto object_schema = realm->schema().find("object");
- REQUIRE(object_schema->persisted_properties.size() == 1);
- REQUIRE(object_schema->persisted_properties[0].table_column == 0);
- object_schema = realm->schema().find("second object");
- REQUIRE(object_schema->persisted_properties.size() == 1);
- REQUIRE(object_schema->persisted_properties[0].table_column == size_t(-1));
- }
- SECTION("extra columns in table") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- {"value 2", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- }
- }
- SECTION("disallowed mismatches") {
- SECTION("missing columns in table") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- {"value 2", PropertyType::Int},
- }},
- };
- REQUIRE_THROWS(realm->update_schema(schema));
- }
- SECTION("bump schema version") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = realm_with_schema(schema);
- REQUIRE_THROWS(realm->update_schema(schema, 1));
- }
- }
- }
- TEST_CASE("migration: ReadOnly") {
- TestFile config;
- auto realm_with_schema = [&](Schema schema) {
- {
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(std::move(schema));
- }
- config.schema_mode = SchemaMode::ReadOnlyAlternative;
- return Realm::get_shared_realm(config);
- };
- SECTION("allowed schema mismatches") {
- SECTION("index") {
- auto realm = realm_with_schema({
- {"object", {
- {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
- {"unindexed", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"indexed", PropertyType::Int},
- {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- REQUIRE(realm->schema() == schema);
- for (auto& object_schema : realm->schema()) {
- for (size_t i = 0; i < object_schema.persisted_properties.size(); ++i) {
- REQUIRE(i == object_schema.persisted_properties[i].table_column);
- }
- }
- }
- SECTION("extra tables") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- {"object 2", {
- {"value", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- }
- SECTION("extra columns in table") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- {"value 2", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- }
- SECTION("missing tables") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- {"second object", {
- {"value", PropertyType::Int},
- }},
- };
- REQUIRE_NOTHROW(realm->update_schema(schema));
- }
- SECTION("bump schema version") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- };
- auto realm = realm_with_schema(schema);
- REQUIRE_NOTHROW(realm->update_schema(schema, 1));
- }
- }
- SECTION("disallowed mismatches") {
- SECTION("missing columns in table") {
- auto realm = realm_with_schema({
- {"object", {
- {"value", PropertyType::Int},
- }},
- });
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- {"value 2", PropertyType::Int},
- }},
- };
- REQUIRE_THROWS(realm->update_schema(schema));
- }
- }
- }
- TEST_CASE("migration: ResetFile") {
- TestFile config;
- config.schema_mode = SchemaMode::ResetFile;
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int},
- }},
- {"object 2", {
- {"value", PropertyType::Int},
- }},
- };
- // To verify that the file has actually be deleted and recreated, on
- // non-Windows we need to hold an open file handle to the old file to force
- // using a new inode, but on Windows we *can't*
- #ifdef _WIN32
- auto get_fileid = [&] {
- // this is wrong for non-ascii but it's what core does
- std::wstring ws(config.path.begin(), config.path.end());
- HANDLE handle = CreateFile2(ws.c_str(), GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING,
- nullptr);
- REQUIRE(handle != INVALID_HANDLE_VALUE);
- auto close = util::make_scope_exit([=]() noexcept { CloseHandle(handle); });
- BY_HANDLE_FILE_INFORMATION info{};
- REQUIRE(GetFileInformationByHandle(handle, &info));
- return (DWORDLONG)info.nFileIndexHigh + (DWORDLONG)info.nFileIndexLow;
- };
- #else
- auto get_fileid = [&] {
- util::File::UniqueID id;
- util::File::get_unique_id(config.path, id);
- return id.inode;
- };
- File holder(config.path, File::mode_Write);
- #endif
- {
- auto realm = Realm::get_shared_realm(config);
- auto ino = get_fileid();
- realm->update_schema(schema);
- REQUIRE(ino == get_fileid());
- realm->begin_transaction();
- ObjectStore::table_for_object_type(realm->read_group(), "object")->add_empty_row();
- realm->commit_transaction();
- }
- auto realm = Realm::get_shared_realm(config);
- auto ino = get_fileid();
- SECTION("file is reset when schema version increases") {
- realm->update_schema(schema, 1);
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0);
- REQUIRE(ino != get_fileid());
- }
- SECTION("file is reset when an existing table is modified") {
- realm->update_schema(add_property(schema, "object",
- {"value 2", PropertyType::Int}));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0);
- REQUIRE(ino != get_fileid());
- }
- SECTION("file is not reset when adding a new table") {
- realm->update_schema(add_table(schema, {"object 3", {
- {"value", PropertyType::Int},
- }}));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
- REQUIRE(realm->schema().size() == 3);
- REQUIRE(ino == get_fileid());
- }
- SECTION("file is not reset when removing a table") {
- realm->update_schema(remove_table(schema, "object 2"));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2"));
- REQUIRE(realm->schema().size() == 1);
- REQUIRE(ino == get_fileid());
- }
- SECTION("file is not reset when adding an index") {
- realm->update_schema(set_indexed(schema, "object", "value", true));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
- REQUIRE(ino == get_fileid());
- }
- SECTION("file is not reset when removing an index") {
- realm->update_schema(set_indexed(schema, "object", "value", true));
- realm->update_schema(schema);
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
- REQUIRE(ino == get_fileid());
- }
- }
- TEST_CASE("migration: Additive") {
- Schema schema = {
- {"object", {
- {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
- {"value 2", PropertyType::Int|PropertyType::Nullable},
- }},
- };
- TestFile config;
- config.schema_mode = SchemaMode::Additive;
- config.cache = false;
- config.schema = schema;
- auto realm = Realm::get_shared_realm(config);
- realm->update_schema(schema);
- SECTION("can add new properties to existing tables") {
- REQUIRE_NOTHROW(realm->update_schema(add_property(schema, "object",
- {"value 3", PropertyType::Int})));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 3);
- }
- SECTION("can add new tables") {
- REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
- {"value", PropertyType::Int},
- }})));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object"));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2"));
- }
- SECTION("indexes are updated when schema version is bumped") {
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- REQUIRE(table->has_search_index(0));
- REQUIRE(!table->has_search_index(1));
- REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false), 1));
- REQUIRE(!table->has_search_index(0));
- REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true), 2));
- REQUIRE(table->has_search_index(1));
- }
- SECTION("indexes are not updated when schema version is not bumped") {
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- REQUIRE(table->has_search_index(0));
- REQUIRE(!table->has_search_index(1));
- REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false)));
- REQUIRE(table->has_search_index(0));
- REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true)));
- REQUIRE(!table->has_search_index(1));
- }
- SECTION("can remove properties from existing tables, but column is not removed") {
- auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
- REQUIRE_NOTHROW(realm->update_schema(remove_property(schema, "object", "value")));
- REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 2);
- auto const& properties = realm->schema().find("object")->persisted_properties;
- REQUIRE(properties.size() == 1);
- REQUIRE(properties[0].table_column == 1);
- }
- SECTION("cannot change existing property types") {
- REQUIRE_THROWS(realm->update_schema(set_type(schema, "object", "value", PropertyType::Float)));
- }
- SECTION("cannot change existing property nullability") {
- REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value", true)));
- REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value 2", false)));
- }
- SECTION("cannot change existing link targets") {
- REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
- {"link", PropertyType::Object|PropertyType::Nullable, "object"},
- }})));
- REQUIRE_THROWS(realm->update_schema(set_target(realm->schema(), "object 2", "link", "object 2")));
- }
- SECTION("cannot change primary keys") {
- REQUIRE_THROWS(realm->update_schema(set_primary_key(schema, "object", "value")));
- REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- }})));
- REQUIRE_THROWS(realm->update_schema(set_primary_key(realm->schema(), "object 2", "")));
- }
- SECTION("schema version is allowed to go down") {
- REQUIRE_NOTHROW(realm->update_schema(schema, 1));
- REQUIRE(realm->schema_version() == 1);
- REQUIRE_NOTHROW(realm->update_schema(schema, 0));
- REQUIRE(realm->schema_version() == 1);
- }
- SECTION("migration function is not used") {
- REQUIRE_NOTHROW(realm->update_schema(schema, 1,
- [&](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); }));
- }
- SECTION("add new columns at end from different SG") {
- auto realm2 = Realm::get_shared_realm(config);
- auto& group = realm2->read_group();
- realm2->begin_transaction();
- auto table = ObjectStore::table_for_object_type(group, "object");
- table->add_column(type_Int, "new column");
- realm2->commit_transaction();
- REQUIRE_NOTHROW(realm->refresh());
- REQUIRE(realm->schema() == schema);
- REQUIRE(realm->schema().find("object")->persisted_properties[0].table_column == 0);
- REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 1);
- }
- SECTION("add new columns at beginning from different SG") {
- auto realm2 = Realm::get_shared_realm(config);
- auto& group = realm2->read_group();
- realm2->begin_transaction();
- auto table = ObjectStore::table_for_object_type(group, "object");
- table->insert_column(0, type_Int, "new column");
- realm2->commit_transaction();
- REQUIRE_NOTHROW(realm->refresh());
- REQUIRE(realm->schema() == schema);
- REQUIRE(realm->schema().find("object")->persisted_properties[0].table_column == 1);
- REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 2);
- }
- SECTION("opening new Realms uses the correct schema after an external change") {
- auto realm2 = Realm::get_shared_realm(config);
- auto& group = realm2->read_group();
- realm2->begin_transaction();
- auto table = ObjectStore::table_for_object_type(group, "object");
- table->insert_column(0, type_Double, "newcol");
- realm2->commit_transaction();
- REQUIRE_NOTHROW(realm->refresh());
- REQUIRE(realm->schema() == schema);
- REQUIRE(realm->schema().find("object")->persisted_properties[0].table_column == 1);
- REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 2);
- // Gets the schema from the RealmCoordinator
- auto realm3 = Realm::get_shared_realm(config);
- REQUIRE(realm3->schema().find("object")->persisted_properties[0].table_column == 1);
- REQUIRE(realm3->schema().find("object")->persisted_properties[1].table_column == 2);
- // Close and re-open the file entirely so that the coordinator is recreated
- realm.reset();
- realm2.reset();
- realm3.reset();
- realm = Realm::get_shared_realm(config);
- REQUIRE(realm->schema() == schema);
- REQUIRE(realm->schema().find("object")->persisted_properties[0].table_column == 1);
- REQUIRE(realm->schema().find("object")->persisted_properties[1].table_column == 2);
- }
- SECTION("can have different subsets of columns in different Realm instances") {
- auto config2 = config;
- config2.schema = add_property(schema, "object",
- {"value 3", PropertyType::Int});
- auto config3 = config;
- config3.schema = remove_property(schema, "object", "value 2");
- auto config4 = config;
- config4.schema = util::none;
- auto realm2 = Realm::get_shared_realm(config2);
- auto realm3 = Realm::get_shared_realm(config3);
- REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
- REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3);
- REQUIRE(realm3->schema().find("object")->persisted_properties.size() == 1);
- realm->refresh();
- realm2->refresh();
- REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
- REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3);
- // No schema specified; should see all of them
- auto realm4 = Realm::get_shared_realm(config4);
- REQUIRE(realm4->schema().find("object")->persisted_properties.size() == 3);
- }
- SECTION("updating a schema to include already-present column") {
- auto config2 = config;
- config2.schema = add_property(schema, "object",
- {"value 3", PropertyType::Int});
- auto realm2 = Realm::get_shared_realm(config2);
- REQUIRE_NOTHROW(realm->update_schema(*config2.schema));
- REQUIRE(realm->schema().find("object")->persisted_properties.size() == 3);
- auto& properties = realm->schema().find("object")->persisted_properties;
- REQUIRE(properties[0].table_column == 0);
- REQUIRE(properties[1].table_column == 1);
- REQUIRE(properties[2].table_column == 2);
- }
- SECTION("increasing schema version without modifying schema properly leaves the schema untouched") {
- TestFile config1;
- config1.schema = schema;
- config1.schema_mode = SchemaMode::Additive;
- config1.schema_version = 0;
- auto realm1 = Realm::get_shared_realm(config1);
- REQUIRE(realm1->schema().size() == 1);
- Schema schema1 = realm1->schema();
- realm1->close();
- auto config2 = config1;
- config2.schema_version = 1;
- auto realm2 = Realm::get_shared_realm(config2);
- REQUIRE(realm2->schema() == schema1);
- }
- SECTION("invalid schema update leaves the schema untouched") {
- auto config2 = config;
- config2.schema = add_property(schema, "object", {"value 3", PropertyType::Int});
- auto realm2 = Realm::get_shared_realm(config2);
- REQUIRE_THROWS(realm->update_schema(add_property(schema, "object", {"value 3", PropertyType::Float})));
- REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
- }
- SECTION("update_schema() does not begin a write transaction when extra columns are present") {
- realm->begin_transaction();
- auto realm2 = Realm::get_shared_realm(config);
- // will deadlock if it tries to start a write transaction
- realm2->update_schema(remove_property(schema, "object", "value"));
- }
- SECTION("update_schema() does not begin a write transaction when indexes are changed without bumping schema version") {
- realm->begin_transaction();
- auto realm2 = Realm::get_shared_realm(config);
- // will deadlock if it tries to start a write transaction
- realm->update_schema(set_indexed(schema, "object", "value 2", true));
- }
- SECTION("update_schema() does not begin a write transaction for invalid schema changes") {
- realm->begin_transaction();
- auto realm2 = Realm::get_shared_realm(config);
- auto new_schema = add_property(remove_property(schema, "object", "value"),
- "object", {"value", PropertyType::Float});
- // will deadlock if it tries to start a write transaction
- REQUIRE_THROWS(realm2->update_schema(new_schema));
- }
- }
- TEST_CASE("migration: Manual") {
- TestFile config;
- config.schema_mode = SchemaMode::Manual;
- auto realm = Realm::get_shared_realm(config);
- Schema schema = {
- {"object", {
- {"pk", PropertyType::Int, Property::IsPrimary{true}},
- {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
- {"optional", PropertyType::Int|PropertyType::Nullable},
- }},
- {"link origin", {
- {"not a pk", PropertyType::Int},
- {"object", PropertyType::Object|PropertyType::Nullable, "object"},
- {"array", PropertyType::Array|PropertyType::Object, "object"},
- }}
- };
- realm->update_schema(schema);
- #define REQUIRE_MIGRATION(schema, migration) do { \
- Schema new_schema = (schema); \
- REQUIRE_THROWS(realm->update_schema(new_schema)); \
- REQUIRE(realm->schema_version() == 0); \
- REQUIRE_THROWS(realm->update_schema(new_schema, 1, [](SharedRealm, SharedRealm, Schema&){})); \
- REQUIRE(realm->schema_version() == 0); \
- REQUIRE_NOTHROW(realm->update_schema(new_schema, 1, migration)); \
- REQUIRE(realm->schema_version() == 1); \
- } while (false)
- SECTION("add new table") {
- REQUIRE_MIGRATION(add_table(schema, {"new table", {
- {"value", PropertyType::Int},
- }}), [](SharedRealm, SharedRealm realm, Schema&) {
- realm->read_group().add_table("class_new table")->add_column(type_Int, "value");
- });
- }
- SECTION("add property to table") {
- REQUIRE_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}),
- [](SharedRealm, SharedRealm realm, Schema&) {
- get_table(realm, "object")->add_column(type_Int, "new");
- });
- }
- SECTION("remove property from table") {
- REQUIRE_MIGRATION(remove_property(schema, "object", "value"),
- [](SharedRealm, SharedRealm realm, Schema&) {
- get_table(realm, "object")->remove_column(1);
- });
- }
- SECTION("add primary key to table") {
- REQUIRE_MIGRATION(set_primary_key(schema, "link origin", "not a pk"),
- [](SharedRealm, SharedRealm realm, Schema&) {
- ObjectStore::set_primary_key_for_object(realm->read_group(), "link origin", "not a pk");
- get_table(realm, "link origin")->add_search_index(0);
- });
- }
- SECTION("remove primary key from table") {
- REQUIRE_MIGRATION(set_primary_key(schema, "object", ""),
- [](SharedRealm, SharedRealm realm, Schema&) {
- ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "");
- get_table(realm, "object")->remove_search_index(0);
- });
- }
- SECTION("change primary key") {
- REQUIRE_MIGRATION(set_primary_key(schema, "object", "value"),
- [](SharedRealm, SharedRealm realm, Schema&) {
- ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "value");
- auto table = get_table(realm, "object");
- table->remove_search_index(0);
- table->add_search_index(1);
- });
- }
- SECTION("change property type") {
- REQUIRE_MIGRATION(set_type(schema, "object", "value", PropertyType::Date),
- [](SharedRealm, SharedRealm realm, Schema&) {
- auto table = get_table(realm, "object");
- table->remove_column(1);
- size_t col = table->add_column(type_Timestamp, "value");
- table->add_search_index(col);
- });
- }
- SECTION("change link target") {
- REQUIRE_MIGRATION(set_target(schema, "link origin", "object", "link origin"),
- [](SharedRealm, SharedRealm realm, Schema&) {
- auto table = get_table(realm, "link origin");
- table->remove_column(1);
- table->add_column_link(type_Link, "object", *table);
- });
- }
- SECTION("change linklist target") {
- REQUIRE_MIGRATION(set_target(schema, "link origin", "array", "link origin"),
- [](SharedRealm, SharedRealm realm, Schema&) {
- auto table = get_table(realm, "link origin");
- table->remove_column(2);
- table->add_column_link(type_LinkList, "array", *table);
- });
- }
- SECTION("make property optional") {
- REQUIRE_MIGRATION(set_optional(schema, "object", "value", true),
- [](SharedRealm, SharedRealm realm, Schema&) {
- auto table = get_table(realm, "object");
- table->remove_column(1);
- size_t col = table->add_column(type_Int, "value", true);
- table->add_search_index(col);
- });
- }
- SECTION("make property required") {
- REQUIRE_MIGRATION(set_optional(schema, "object", "optional", false),
- [](SharedRealm, SharedRealm realm, Schema&) {
- auto table = get_table(realm, "object");
- table->remove_column(2);
- table->add_column(type_Int, "optional", false);
- });
- }
- SECTION("add index") {
- REQUIRE_MIGRATION(set_indexed(schema, "object", "optional", true),
- [](SharedRealm, SharedRealm realm, Schema&) {
- get_table(realm, "object")->add_search_index(2);
- });
- }
- SECTION("remove index") {
- REQUIRE_MIGRATION(set_indexed(schema, "object", "value", false),
- [](SharedRealm, SharedRealm realm, Schema&) {
- get_table(realm, "object")->remove_search_index(1);
- });
- }
- SECTION("reorder properties") {
- auto schema2 = schema;
- auto& properties = schema2.find("object")->persisted_properties;
- std::swap(properties[0], properties[1]);
- REQUIRE_NOTHROW(realm->update_schema(schema2));
- }
- SECTION("cannot lower schema version") {
- REQUIRE_NOTHROW(realm->update_schema(schema, 1, [](SharedRealm, SharedRealm, Schema&){}));
- REQUIRE(realm->schema_version() == 1);
- REQUIRE_THROWS(realm->update_schema(schema, 0, [](SharedRealm, SharedRealm, Schema&){}));
- REQUIRE(realm->schema_version() == 1);
- }
- SECTION("update_schema() does not begin a write transaction when schema version is unchanged") {
- realm->begin_transaction();
- auto realm2 = Realm::get_shared_realm(config);
- // will deadlock if it tries to start a write transaction
- REQUIRE_NOTHROW(realm2->update_schema(schema));
- REQUIRE_THROWS(realm2->update_schema(remove_property(schema, "object", "value")));
- }
- SECTION("null migration callback should throw SchemaMismatchException") {
- Schema new_schema = remove_property(schema, "object", "value");
- REQUIRE_THROWS_AS(realm->update_schema(new_schema, 1, nullptr), SchemaMismatchException);
- }
- }
|