migrations.cpp 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #include "catch2/catch.hpp"
  19. #include "util/test_file.hpp"
  20. #include "object_schema.hpp"
  21. #include "object_store.hpp"
  22. #include "property.hpp"
  23. #include "schema.hpp"
  24. #include "impl/object_accessor_impl.hpp"
  25. #include <realm/group.hpp>
  26. #include <realm/table.hpp>
  27. #include <realm/util/scope_exit.hpp>
  28. #ifdef _WIN32
  29. #include <Windows.h>
  30. #endif
  31. using namespace realm;
  32. #define VERIFY_SCHEMA(r, m) verify_schema((r), __LINE__, m)
  33. #define REQUIRE_UPDATE_SUCCEEDS(r, s, version) do { \
  34. REQUIRE_NOTHROW((r).update_schema(s, version)); \
  35. VERIFY_SCHEMA(r, false); \
  36. REQUIRE((r).schema() == s); \
  37. } while (0)
  38. #define REQUIRE_NO_MIGRATION_NEEDED(r, schema1, schema2) do { \
  39. REQUIRE_UPDATE_SUCCEEDS(r, schema1, 0); \
  40. REQUIRE_UPDATE_SUCCEEDS(r, schema2, 0); \
  41. } while (0)
  42. #define REQUIRE_MIGRATION_NEEDED(r, schema1, schema2) do { \
  43. REQUIRE_UPDATE_SUCCEEDS(r, schema1, 0); \
  44. REQUIRE_THROWS((r).update_schema(schema2)); \
  45. REQUIRE((r).schema() == schema1); \
  46. REQUIRE_UPDATE_SUCCEEDS(r, schema2, 1); \
  47. } while (0)
  48. namespace {
  49. void verify_schema(Realm& r, int line, bool in_migration)
  50. {
  51. CAPTURE(line);
  52. for (auto&& object_schema : r.schema()) {
  53. auto table = r.read_group().get_table(object_schema.table_key);
  54. REQUIRE(table);
  55. REQUIRE(std::string(table->get_name()) == ObjectStore::table_name_for_object_type(object_schema.name));
  56. CAPTURE(object_schema.name);
  57. std::string primary_key;
  58. if (!in_migration) {
  59. primary_key = ObjectStore::get_primary_key_for_object(r.read_group(), object_schema.name);
  60. REQUIRE(primary_key == object_schema.primary_key);
  61. }
  62. else {
  63. primary_key = object_schema.primary_key;
  64. }
  65. for (auto&& prop : object_schema.persisted_properties) {
  66. auto col = table->get_column_key(prop.name);
  67. CAPTURE(prop.name);
  68. REQUIRE(col);
  69. REQUIRE(col == prop.column_key);
  70. REQUIRE(to_underlying(ObjectSchema::from_core_type(*table, col)) ==
  71. to_underlying(prop.type));
  72. REQUIRE(table->has_search_index(col) == prop.requires_index());
  73. REQUIRE(bool(prop.is_primary) == (prop.name == primary_key));
  74. }
  75. }
  76. }
  77. TableRef get_table(std::shared_ptr<Realm> const& realm, StringData object_type)
  78. {
  79. return ObjectStore::table_for_object_type(realm->read_group(), object_type);
  80. }
  81. // Helper functions for modifying Schema objects, mostly for the sake of making
  82. // it clear what exactly is different about the 2+ schema objects used in
  83. // various tests
  84. Schema add_table(Schema const& schema, ObjectSchema object_schema)
  85. {
  86. std::vector<ObjectSchema> new_schema(schema.begin(), schema.end());
  87. new_schema.push_back(std::move(object_schema));
  88. return new_schema;
  89. }
  90. Schema remove_table(Schema const& schema, StringData object_name)
  91. {
  92. std::vector<ObjectSchema> new_schema;
  93. std::remove_copy_if(schema.begin(), schema.end(), std::back_inserter(new_schema),
  94. [&](auto&& object_schema) { return object_schema.name == object_name; });
  95. return new_schema;
  96. }
  97. Schema add_property(Schema schema, StringData object_name, Property property)
  98. {
  99. schema.find(object_name)->persisted_properties.push_back(std::move(property));
  100. return schema;
  101. }
  102. Schema remove_property(Schema schema, StringData object_name, StringData property_name)
  103. {
  104. auto& properties = schema.find(object_name)->persisted_properties;
  105. properties.erase(find_if(begin(properties), end(properties),
  106. [&](auto&& prop) { return prop.name == property_name; }));
  107. return schema;
  108. }
  109. Schema set_indexed(Schema schema, StringData object_name, StringData property_name, bool value)
  110. {
  111. schema.find(object_name)->property_for_name(property_name)->is_indexed = value;
  112. return schema;
  113. }
  114. Schema set_optional(Schema schema, StringData object_name, StringData property_name, bool value)
  115. {
  116. auto& prop = *schema.find(object_name)->property_for_name(property_name);
  117. if (value)
  118. prop.type |= PropertyType::Nullable;
  119. else
  120. prop.type &= ~PropertyType::Nullable;
  121. return schema;
  122. }
  123. Schema set_type(Schema schema, StringData object_name, StringData property_name, PropertyType value)
  124. {
  125. schema.find(object_name)->property_for_name(property_name)->type = value;
  126. return schema;
  127. }
  128. Schema set_target(Schema schema, StringData object_name, StringData property_name, StringData new_target)
  129. {
  130. schema.find(object_name)->property_for_name(property_name)->object_type = new_target;
  131. return schema;
  132. }
  133. Schema set_primary_key(Schema schema, StringData object_name, StringData new_primary_property)
  134. {
  135. auto& object_schema = *schema.find(object_name);
  136. if (auto old_primary = object_schema.primary_key_property()) {
  137. old_primary->is_primary = false;
  138. }
  139. if (new_primary_property.size()) {
  140. object_schema.property_for_name(new_primary_property)->is_primary = true;
  141. }
  142. object_schema.primary_key = new_primary_property;
  143. return schema;
  144. }
  145. auto create_objects(Table& table, size_t count) {
  146. std::vector<ObjKey> keys;
  147. table.create_objects(count, keys);
  148. return keys;
  149. };
  150. } // anonymous namespace
  151. TEST_CASE("migration: Automatic") {
  152. InMemoryTestFile config;
  153. config.automatic_change_notifications = false;
  154. SECTION("no migration required") {
  155. SECTION("add object schema") {
  156. auto realm = Realm::get_shared_realm(config);
  157. Schema schema1 = {};
  158. Schema schema2 = add_table(schema1, {"object", {
  159. {"value", PropertyType::Int}
  160. }});
  161. Schema schema3 = add_table(schema2, {"object2", {
  162. {"value", PropertyType::Int}
  163. }});
  164. REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
  165. REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 0);
  166. REQUIRE_UPDATE_SUCCEEDS(*realm, schema3, 0);
  167. }
  168. SECTION("remove object schema") {
  169. auto realm = Realm::get_shared_realm(config);
  170. Schema schema1 = {
  171. {"object", {
  172. {"value", PropertyType::Int}
  173. }},
  174. {"object2", {
  175. {"value", PropertyType::Int}
  176. }},
  177. };
  178. Schema schema2 = remove_table(schema1, "object2");
  179. Schema schema3 = remove_table(schema2, "object");
  180. REQUIRE_UPDATE_SUCCEEDS(*realm, schema3, 0);
  181. REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 0);
  182. REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
  183. }
  184. SECTION("add index") {
  185. auto realm = Realm::get_shared_realm(config);
  186. Schema schema = {
  187. {"object", {
  188. {"value", PropertyType::Int}
  189. }},
  190. };
  191. REQUIRE_NO_MIGRATION_NEEDED(*realm, schema, set_indexed(schema, "object", "value", true));
  192. }
  193. SECTION("remove index") {
  194. auto realm = Realm::get_shared_realm(config);
  195. Schema schema = {
  196. {"object", {
  197. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}}
  198. }},
  199. };
  200. REQUIRE_NO_MIGRATION_NEEDED(*realm, schema, set_indexed(schema, "object", "value", false));
  201. }
  202. SECTION("reordering properties") {
  203. auto realm = Realm::get_shared_realm(config);
  204. Schema schema1 = {
  205. {"object", {
  206. {"col1", PropertyType::Int},
  207. {"col2", PropertyType::Int},
  208. }},
  209. };
  210. Schema schema2 = {
  211. {"object", {
  212. {"col2", PropertyType::Int},
  213. {"col1", PropertyType::Int},
  214. }},
  215. };
  216. REQUIRE_NO_MIGRATION_NEEDED(*realm, schema1, schema2);
  217. }
  218. }
  219. SECTION("migration required") {
  220. SECTION("add property to existing object schema") {
  221. auto realm = Realm::get_shared_realm(config);
  222. Schema schema1 = {
  223. {"object", {
  224. {"col1", PropertyType::Int},
  225. }},
  226. };
  227. auto schema2 = add_property(schema1, "object",
  228. {"col2", PropertyType::Int});
  229. REQUIRE_MIGRATION_NEEDED(*realm, schema1, schema2);
  230. }
  231. SECTION("remove property from existing object schema") {
  232. auto realm = Realm::get_shared_realm(config);
  233. Schema schema = {
  234. {"object", {
  235. {"col1", PropertyType::Int},
  236. {"col2", PropertyType::Int},
  237. }},
  238. };
  239. REQUIRE_MIGRATION_NEEDED(*realm, schema, remove_property(schema, "object", "col2"));
  240. }
  241. SECTION("migratation which replaces a persisted property with a computed one") {
  242. auto realm = Realm::get_shared_realm(config);
  243. Schema schema1 = {
  244. {"object", {
  245. {"value", PropertyType::Int},
  246. {"link", PropertyType::Object|PropertyType::Nullable, "object2"},
  247. }},
  248. {"object2", {
  249. {"value", PropertyType::Int},
  250. {"inverse", PropertyType::Object|PropertyType::Nullable, "object"},
  251. }},
  252. };
  253. Schema schema2 = remove_property(schema1, "object", "link");
  254. Property new_property{"link", PropertyType::LinkingObjects|PropertyType::Array, "object2", "inverse"};
  255. schema2.find("object")->computed_properties.emplace_back(new_property);
  256. REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
  257. REQUIRE_THROWS((*realm).update_schema(schema2));
  258. REQUIRE((*realm).schema() == schema1);
  259. REQUIRE_NOTHROW((*realm).update_schema(schema2, 1,
  260. [](SharedRealm, SharedRealm, Schema&) { /* empty but present migration handler */ }));
  261. VERIFY_SCHEMA(*realm, false);
  262. REQUIRE((*realm).schema() == schema2);
  263. }
  264. SECTION("change property type") {
  265. auto realm = Realm::get_shared_realm(config);
  266. Schema schema = {
  267. {"object", {
  268. {"value", PropertyType::Int},
  269. }},
  270. };
  271. REQUIRE_MIGRATION_NEEDED(*realm, schema, set_type(schema, "object", "value", PropertyType::Float));
  272. }
  273. SECTION("make property nullable") {
  274. auto realm = Realm::get_shared_realm(config);
  275. Schema schema = {
  276. {"object", {
  277. {"value", PropertyType::Int},
  278. }},
  279. };
  280. REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", true));
  281. }
  282. SECTION("make property required") {
  283. auto realm = Realm::get_shared_realm(config);
  284. Schema schema = {
  285. {"object", {
  286. {"value", PropertyType::Int|PropertyType::Nullable},
  287. }},
  288. };
  289. REQUIRE_MIGRATION_NEEDED(*realm, schema, set_optional(schema, "object", "value", false));
  290. }
  291. SECTION("change link target") {
  292. auto realm = Realm::get_shared_realm(config);
  293. Schema schema = {
  294. {"target 1", {
  295. {"value", PropertyType::Int},
  296. }},
  297. {"target 2", {
  298. {"value", PropertyType::Int},
  299. }},
  300. {"origin", {
  301. {"value", PropertyType::Object|PropertyType::Nullable, "target 1"},
  302. }},
  303. };
  304. REQUIRE_MIGRATION_NEEDED(*realm, schema, set_target(schema, "origin", "value", "target 2"));
  305. }
  306. SECTION("add pk") {
  307. auto realm = Realm::get_shared_realm(config);
  308. Schema schema = {
  309. {"object", {
  310. {"value", PropertyType::Int},
  311. }},
  312. };
  313. REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", "value"));
  314. }
  315. SECTION("remove pk") {
  316. auto realm = Realm::get_shared_realm(config);
  317. Schema schema = {
  318. {"object", {
  319. {"value", PropertyType::Int, Property::IsPrimary{true}},
  320. }},
  321. };
  322. REQUIRE_MIGRATION_NEEDED(*realm, schema, set_primary_key(schema, "object", ""));
  323. }
  324. SECTION("adding column and table in same migration doesn't add duplicate columns") {
  325. auto realm = Realm::get_shared_realm(config);
  326. Schema schema1 = {
  327. {"object", {
  328. {"col1", PropertyType::Int},
  329. }},
  330. };
  331. auto schema2 = add_table(add_property(schema1, "object", {"col2", PropertyType::Int}),
  332. {"object2", {{"value", PropertyType::Int}}});
  333. REQUIRE_UPDATE_SUCCEEDS(*realm, schema1, 0);
  334. REQUIRE_UPDATE_SUCCEEDS(*realm, schema2, 1);
  335. auto& table = *get_table(realm, "object2");
  336. REQUIRE(table.get_column_count() == 1);
  337. }
  338. }
  339. SECTION("migration block invocations") {
  340. SECTION("not called for initial creation of schema") {
  341. Schema schema = {
  342. {"object", {
  343. {"value", PropertyType::Int},
  344. }},
  345. };
  346. auto realm = Realm::get_shared_realm(config);
  347. realm->update_schema(schema, 5, [](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); });
  348. }
  349. SECTION("not called when schema version is unchanged even if there are schema changes") {
  350. Schema schema1 = {
  351. {"object", {
  352. {"value", PropertyType::Int},
  353. }},
  354. };
  355. Schema schema2 = add_table(schema1, {"second object", {
  356. {"value", PropertyType::Int},
  357. }});
  358. auto realm = Realm::get_shared_realm(config);
  359. realm->update_schema(schema1, 1);
  360. realm->update_schema(schema2, 1, [](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); });
  361. }
  362. SECTION("called when schema version is bumped even if there are no schema changes") {
  363. Schema schema = {
  364. {"object", {
  365. {"value", PropertyType::Int},
  366. }},
  367. };
  368. auto realm = Realm::get_shared_realm(config);
  369. realm->update_schema(schema);
  370. bool called = false;
  371. realm->update_schema(schema, 5, [&](SharedRealm, SharedRealm, Schema&) { called = true; });
  372. REQUIRE(called);
  373. }
  374. }
  375. SECTION("migration errors") {
  376. SECTION("schema version cannot go down") {
  377. auto realm = Realm::get_shared_realm(config);
  378. realm->update_schema({}, 1);
  379. realm->update_schema({}, 2);
  380. REQUIRE_THROWS(realm->update_schema({}, 0));
  381. }
  382. SECTION("insert duplicate keys for existing PK during migration") {
  383. Schema schema = {
  384. {"object", {
  385. {"value", PropertyType::Int, Property::IsPrimary{true}},
  386. }},
  387. };
  388. auto realm = Realm::get_shared_realm(config);
  389. realm->update_schema(schema, 1);
  390. REQUIRE_THROWS(realm->update_schema(schema, 2, [](SharedRealm, SharedRealm realm, Schema&) {
  391. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  392. create_objects(*table, 2);
  393. }));
  394. }
  395. SECTION("add pk to existing table with duplicate keys") {
  396. Schema schema = {
  397. {"object", {
  398. {"value", PropertyType::Int},
  399. }},
  400. };
  401. auto realm = Realm::get_shared_realm(config);
  402. realm->update_schema(schema, 1);
  403. realm->begin_transaction();
  404. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  405. create_objects(*table, 2);
  406. realm->commit_transaction();
  407. schema = set_primary_key(schema, "object", "value");
  408. REQUIRE_THROWS(realm->update_schema(schema, 2, nullptr));
  409. }
  410. SECTION("throwing an exception from migration function rolls back all changes") {
  411. Schema schema1 = {
  412. {"object", {
  413. {"value", PropertyType::Int},
  414. }},
  415. };
  416. Schema schema2 = add_property(schema1, "object",
  417. {"value2", PropertyType::Int});
  418. auto realm = Realm::get_shared_realm(config);
  419. realm->update_schema(schema1, 1);
  420. REQUIRE_THROWS(realm->update_schema(schema2, 2, [](SharedRealm, SharedRealm realm, Schema&) {
  421. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  422. table->create_object();
  423. throw 5;
  424. }));
  425. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  426. REQUIRE(table->size() == 0);
  427. REQUIRE(realm->schema_version() == 1);
  428. REQUIRE(realm->schema() == schema1);
  429. }
  430. }
  431. SECTION("valid migrations") {
  432. SECTION("changing all columns does not lose row count") {
  433. Schema schema = {
  434. {"object", {
  435. {"value", PropertyType::Int},
  436. }},
  437. };
  438. auto realm = Realm::get_shared_realm(config);
  439. realm->update_schema(schema, 1);
  440. realm->begin_transaction();
  441. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  442. create_objects(*table, 10);
  443. realm->commit_transaction();
  444. schema = set_type(schema, "object", "value", PropertyType::Float);
  445. realm->update_schema(schema, 2);
  446. REQUIRE(table->size() == 10);
  447. }
  448. SECTION("values for required properties are copied when converitng to nullable") {
  449. Schema schema = {
  450. {"object", {
  451. {"value", PropertyType::Int},
  452. }},
  453. };
  454. auto realm = Realm::get_shared_realm(config);
  455. realm->update_schema(schema, 1);
  456. realm->begin_transaction();
  457. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  458. auto key = table->get_column_key("value");
  459. create_objects(*table, 10);
  460. for (int i = 0; i < 10; ++i)
  461. table->get_object(i).set(key, i);
  462. realm->commit_transaction();
  463. realm->update_schema(set_optional(schema, "object", "value", true), 2);
  464. key = table->get_column_key("value");
  465. for (int i = 0; i < 10; ++i)
  466. REQUIRE(table->get_object(i).get<util::Optional<int64_t>>(key) == i);
  467. }
  468. SECTION("values for nullable properties are discarded when converitng to required") {
  469. Schema schema = {
  470. {"object", {
  471. {"value", PropertyType::Int|PropertyType::Nullable},
  472. }},
  473. };
  474. auto realm = Realm::get_shared_realm(config);
  475. realm->update_schema(schema, 1);
  476. realm->begin_transaction();
  477. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  478. auto key = table->get_column_key("value");
  479. create_objects(*table, 10);
  480. for (int i = 0; i < 10; ++i)
  481. table->get_object(i).set(key, i);
  482. realm->commit_transaction();
  483. realm->update_schema(set_optional(schema, "object", "value", false), 2);
  484. key = table->get_column_key("value");
  485. for (size_t i = 0; i < 10; ++i)
  486. REQUIRE(table->get_object(i).get<int64_t>(key) == 0);
  487. }
  488. SECTION("deleting table removed from the schema deletes it") {
  489. Schema schema = {
  490. {"object", {
  491. {"value", PropertyType::Int|PropertyType::Nullable},
  492. }},
  493. };
  494. auto realm = Realm::get_shared_realm(config);
  495. realm->update_schema(schema, 1);
  496. realm->update_schema({}, 2, [](SharedRealm, SharedRealm realm, Schema&) {
  497. ObjectStore::delete_data_for_object(realm->read_group(), "object");
  498. });
  499. REQUIRE_FALSE(ObjectStore::table_for_object_type(realm->read_group(), "object"));
  500. }
  501. SECTION("deleting table still in the schema recreates it with no rows") {
  502. Schema schema = {
  503. {"object", {
  504. {"value", PropertyType::Int|PropertyType::Nullable},
  505. }},
  506. };
  507. auto realm = Realm::get_shared_realm(config);
  508. realm->update_schema(schema, 1);
  509. realm->begin_transaction();
  510. ObjectStore::table_for_object_type(realm->read_group(), "object")->create_object();
  511. realm->commit_transaction();
  512. realm->update_schema(schema, 2, [](SharedRealm, SharedRealm realm, Schema&) {
  513. ObjectStore::delete_data_for_object(realm->read_group(), "object");
  514. });
  515. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  516. REQUIRE(table);
  517. REQUIRE(table->size() == 0);
  518. }
  519. SECTION("deleting table which doesn't exist does nothing") {
  520. Schema schema = {
  521. {"object", {
  522. {"value", PropertyType::Int|PropertyType::Nullable},
  523. }},
  524. };
  525. auto realm = Realm::get_shared_realm(config);
  526. realm->update_schema(schema, 1);
  527. REQUIRE_NOTHROW(realm->update_schema({}, 2, [](SharedRealm, SharedRealm realm, Schema&) {
  528. ObjectStore::delete_data_for_object(realm->read_group(), "foo");
  529. }));
  530. }
  531. }
  532. SECTION("schema correctness during migration") {
  533. InMemoryTestFile config;
  534. config.schema_mode = SchemaMode::Automatic;
  535. auto realm = Realm::get_shared_realm(config);
  536. Schema schema = {
  537. {"object", {
  538. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  539. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  540. {"optional", PropertyType::Int|PropertyType::Nullable},
  541. }},
  542. {"link origin", {
  543. {"not a pk", PropertyType::Int},
  544. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  545. {"array", PropertyType::Array|PropertyType::Object, "object"},
  546. }}
  547. };
  548. realm->update_schema(schema);
  549. #define VERIFY_SCHEMA_IN_MIGRATION(target_schema) do { \
  550. Schema new_schema = (target_schema); \
  551. realm->update_schema(new_schema, 1, [&](SharedRealm old_realm, SharedRealm new_realm, Schema&) { \
  552. REQUIRE(old_realm->schema_version() == 0); \
  553. REQUIRE(old_realm->schema() == schema); \
  554. REQUIRE(new_realm->schema_version() == 1); \
  555. REQUIRE(new_realm->schema() == new_schema); \
  556. VERIFY_SCHEMA(*old_realm, true); \
  557. VERIFY_SCHEMA(*new_realm, true); \
  558. }); \
  559. REQUIRE(realm->schema() == new_schema); \
  560. VERIFY_SCHEMA(*realm, false); \
  561. } while (false)
  562. SECTION("add new table") {
  563. VERIFY_SCHEMA_IN_MIGRATION(add_table(schema, {"new table", {
  564. {"value", PropertyType::Int},
  565. }}));
  566. }
  567. SECTION("add property to table") {
  568. VERIFY_SCHEMA_IN_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}));
  569. }
  570. SECTION("remove property from table") {
  571. VERIFY_SCHEMA_IN_MIGRATION(remove_property(schema, "object", "value"));
  572. }
  573. SECTION("remove multiple properties from table") {
  574. VERIFY_SCHEMA_IN_MIGRATION(remove_property(remove_property(schema, "object", "value"), "object", "optional"));
  575. }
  576. SECTION("add primary key to table") {
  577. VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "link origin", "not a pk"));
  578. }
  579. SECTION("remove primary key from table") {
  580. VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "object", ""));
  581. }
  582. SECTION("change primary key") {
  583. VERIFY_SCHEMA_IN_MIGRATION(set_primary_key(schema, "object", "value"));
  584. }
  585. SECTION("change property type") {
  586. VERIFY_SCHEMA_IN_MIGRATION(set_type(schema, "object", "value", PropertyType::Date));
  587. }
  588. SECTION("change link target") {
  589. VERIFY_SCHEMA_IN_MIGRATION(set_target(schema, "link origin", "object", "link origin"));
  590. }
  591. SECTION("change linklist target") {
  592. VERIFY_SCHEMA_IN_MIGRATION(set_target(schema, "link origin", "array", "link origin"));
  593. }
  594. SECTION("make property optional") {
  595. VERIFY_SCHEMA_IN_MIGRATION(set_optional(schema, "object", "value", true));
  596. }
  597. SECTION("make property required") {
  598. VERIFY_SCHEMA_IN_MIGRATION(set_optional(schema, "object", "optional", false));
  599. }
  600. SECTION("add index") {
  601. VERIFY_SCHEMA_IN_MIGRATION(set_indexed(schema, "object", "optional", true));
  602. }
  603. SECTION("remove index") {
  604. VERIFY_SCHEMA_IN_MIGRATION(set_indexed(schema, "object", "value", false));
  605. }
  606. SECTION("reorder properties") {
  607. auto schema2 = schema;
  608. auto& properties = schema2.find("object")->persisted_properties;
  609. std::swap(properties[0], properties[1]);
  610. VERIFY_SCHEMA_IN_MIGRATION(schema2);
  611. }
  612. }
  613. SECTION("object accessors inside migrations") {
  614. using namespace std::string_literals;
  615. Schema schema{
  616. {"all types", {
  617. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  618. {"bool", PropertyType::Bool},
  619. {"int", PropertyType::Int},
  620. {"float", PropertyType::Float},
  621. {"double", PropertyType::Double},
  622. {"string", PropertyType::String},
  623. {"data", PropertyType::Data},
  624. {"date", PropertyType::Date},
  625. {"object", PropertyType::Object|PropertyType::Nullable, "link target"},
  626. {"array", PropertyType::Object|PropertyType::Array, "array target"},
  627. }},
  628. {"link target", {
  629. {"value", PropertyType::Int},
  630. }, {
  631. {"origin", PropertyType::LinkingObjects|PropertyType::Array, "all types", "object"},
  632. }},
  633. {"array target", {
  634. {"value", PropertyType::Int},
  635. }},
  636. };
  637. InMemoryTestFile config;
  638. config.schema_mode = SchemaMode::Automatic;
  639. config.schema = schema;
  640. auto realm = Realm::get_shared_realm(config);
  641. CppContext ctx(realm);
  642. util::Any values = AnyDict{
  643. {"pk", INT64_C(1)},
  644. {"bool", true},
  645. {"int", INT64_C(5)},
  646. {"float", 2.2f},
  647. {"double", 3.3},
  648. {"string", "hello"s},
  649. {"data", "olleh"s},
  650. {"date", Timestamp(10, 20)},
  651. {"object", AnyDict{{"value", INT64_C(10)}}},
  652. {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
  653. };
  654. realm->begin_transaction();
  655. Object::create(ctx, realm, *realm->schema().find("all types"), values);
  656. realm->commit_transaction();
  657. SECTION("read values from old realm") {
  658. Schema schema{
  659. {"all types", {
  660. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  661. }},
  662. };
  663. realm->update_schema(schema, 2, [](auto old_realm, auto new_realm, Schema&) {
  664. CppContext ctx(old_realm);
  665. Object obj = Object::get_for_primary_key(ctx, old_realm, "all types",
  666. util::Any(INT64_C(1)));
  667. REQUIRE(obj.is_valid());
  668. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true);
  669. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5);
  670. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f);
  671. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3);
  672. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello");
  673. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh");
  674. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20));
  675. auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object"));
  676. REQUIRE(link.is_valid());
  677. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10);
  678. auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array"));
  679. REQUIRE(list.size() == 1);
  680. CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array"));
  681. link = any_cast<Object>(list.get(list_ctx, 0));
  682. REQUIRE(link.is_valid());
  683. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20);
  684. CppContext ctx2(new_realm);
  685. obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  686. util::Any(INT64_C(1)));
  687. REQUIRE(obj.is_valid());
  688. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool"));
  689. });
  690. }
  691. SECTION("cannot mutate old realm") {
  692. realm->update_schema(schema, 2, [](auto old_realm, auto, Schema&) {
  693. CppContext ctx(old_realm);
  694. Object obj = Object::get_for_primary_key(ctx, old_realm, "all types",
  695. util::Any(INT64_C(1)));
  696. REQUIRE(obj.is_valid());
  697. REQUIRE_THROWS(obj.set_property_value(ctx, "bool", util::Any(false)));
  698. REQUIRE_THROWS(old_realm->begin_transaction());
  699. });
  700. }
  701. SECTION("cannot read values for removed properties from new realm") {
  702. Schema schema{
  703. {"all types", {
  704. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  705. }},
  706. };
  707. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  708. CppContext ctx(new_realm);
  709. Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  710. util::Any(INT64_C(1)));
  711. REQUIRE(obj.is_valid());
  712. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool"));
  713. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "object"));
  714. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "array"));
  715. });
  716. }
  717. SECTION("read values from new object") {
  718. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  719. CppContext ctx(new_realm);
  720. Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  721. util::Any(INT64_C(1)));
  722. REQUIRE(obj.is_valid());
  723. auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object"));
  724. REQUIRE(link.is_valid());
  725. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10);
  726. auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array"));
  727. REQUIRE(list.size() == 1);
  728. CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array"));
  729. link = any_cast<Object>(list.get(list_ctx, 0));
  730. REQUIRE(link.is_valid());
  731. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20);
  732. });
  733. }
  734. SECTION("read and write values in new object") {
  735. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  736. CppContext ctx(new_realm);
  737. Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  738. util::Any(INT64_C(1)));
  739. REQUIRE(obj.is_valid());
  740. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true);
  741. obj.set_property_value(ctx, "bool", util::Any(false));
  742. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false);
  743. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5);
  744. obj.set_property_value(ctx, "int", util::Any(INT64_C(6)));
  745. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 6);
  746. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f);
  747. obj.set_property_value(ctx, "float", util::Any(1.23f));
  748. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 1.23f);
  749. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3);
  750. obj.set_property_value(ctx, "double", util::Any(1.23));
  751. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 1.23);
  752. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello");
  753. obj.set_property_value(ctx, "string", util::Any("abc"s));
  754. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "abc");
  755. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh");
  756. obj.set_property_value(ctx, "data", util::Any("abc"s));
  757. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "abc");
  758. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20));
  759. obj.set_property_value(ctx, "date", util::Any(Timestamp(1, 2)));
  760. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(1, 2));
  761. Object linked_obj(new_realm, "link target", 0);
  762. Object new_obj(new_realm, get_table(new_realm, "link target")->create_object());
  763. auto linking = any_cast<Results>(linked_obj.get_property_value<util::Any>(ctx, "origin"));
  764. REQUIRE(linking.size() == 1);
  765. REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).obj().get_key()
  766. == linked_obj.obj().get_key());
  767. obj.set_property_value(ctx, "object", util::Any(new_obj));
  768. REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).obj().get_key()
  769. == new_obj.obj().get_key());
  770. REQUIRE(linking.size() == 0);
  771. });
  772. }
  773. SECTION("create object in new realm") {
  774. realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) {
  775. REQUIRE(new_realm->is_in_transaction());
  776. CppContext ctx(new_realm);
  777. any_cast<AnyDict&>(values)["pk"] = INT64_C(2);
  778. Object obj = Object::create(ctx, new_realm, "all types", values);
  779. REQUIRE(get_table(new_realm, "all types")->size() == 2);
  780. REQUIRE(get_table(new_realm, "link target")->size() == 2);
  781. REQUIRE(get_table(new_realm, "array target")->size() == 2);
  782. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "pk")) == 2);
  783. });
  784. }
  785. SECTION("upsert in new realm") {
  786. realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) {
  787. REQUIRE(new_realm->is_in_transaction());
  788. CppContext ctx(new_realm);
  789. any_cast<AnyDict&>(values)["bool"] = false;
  790. Object obj = Object::create(ctx, new_realm, "all types", values, CreatePolicy::UpdateAll);
  791. REQUIRE(get_table(new_realm, "all types")->size() == 1);
  792. REQUIRE(get_table(new_realm, "link target")->size() == 2);
  793. REQUIRE(get_table(new_realm, "array target")->size() == 2);
  794. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false);
  795. });
  796. }
  797. SECTION("change primary key property type") {
  798. schema = set_type(schema, "all types", "pk", PropertyType::String);
  799. // FIXME: changing the primary key of a type with binary columns currently crashes in core
  800. schema = remove_property(schema, "all types", "data");
  801. realm->update_schema(schema, 2, [](auto, auto new_realm, auto&) {
  802. Object obj(new_realm, "all types", 0);
  803. CppContext ctx(new_realm);
  804. obj.set_property_value(ctx, "pk", util::Any("1"s));
  805. });
  806. }
  807. SECTION("set primary key to duplicate values in migration") {
  808. auto bad_migration = [&](auto, auto new_realm, Schema&) {
  809. // shoud be able to create a new object with the same PK
  810. REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values));
  811. REQUIRE(get_table(new_realm, "all types")->size() == 2);
  812. // but it'll fail at the end
  813. };
  814. REQUIRE_THROWS_AS(realm->update_schema(schema, 2, bad_migration), DuplicatePrimaryKeyValueException);
  815. REQUIRE(get_table(realm, "all types")->size() == 1);
  816. auto good_migration = [&](auto, auto new_realm, Schema&) {
  817. REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values));
  818. // Change the old object's PK to elminate the duplication
  819. Object old_obj(new_realm, "all types", 0);
  820. CppContext ctx(new_realm);
  821. old_obj.set_property_value(ctx, "pk", util::Any(INT64_C(5)));
  822. };
  823. REQUIRE_NOTHROW(realm->update_schema(schema, 2, good_migration));
  824. REQUIRE(get_table(realm, "all types")->size() == 2);
  825. }
  826. }
  827. SECTION("property renaming") {
  828. InMemoryTestFile config;
  829. config.schema_mode = SchemaMode::Automatic;
  830. auto realm = Realm::get_shared_realm(config);
  831. struct Rename {
  832. StringData object_type;
  833. StringData old_name;
  834. StringData new_name;
  835. };
  836. auto apply_renames = [&](std::initializer_list<Rename> renames) -> Realm::MigrationFunction {
  837. return [=](SharedRealm, SharedRealm realm, Schema& schema) {
  838. for (auto rename : renames) {
  839. ObjectStore::rename_property(realm->read_group(), schema,
  840. rename.object_type, rename.old_name, rename.new_name);
  841. }
  842. };
  843. };
  844. #define FAILED_RENAME(old_schema, new_schema, error, ...) do { \
  845. realm->update_schema(old_schema, 1); \
  846. REQUIRE_THROWS_WITH(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__})), error); \
  847. } while (false)
  848. Schema schema = {
  849. {"object", {
  850. {"value", PropertyType::Int},
  851. }},
  852. };
  853. SECTION("table does not exist in old schema") {
  854. auto schema2 = add_table(schema, {"object 2", {
  855. {"value 2", PropertyType::Int},
  856. }});
  857. FAILED_RENAME(schema, schema2,
  858. "Cannot rename property 'object 2.value' because it does not exist.",
  859. {"object 2", "value", "value 2"});
  860. }
  861. SECTION("table does not exist in new schema") {
  862. FAILED_RENAME(schema, {},
  863. "Cannot rename properties for type 'object' because it has been removed from the Realm.",
  864. {"object", "value", "value 2"});
  865. }
  866. SECTION("property does not exist in old schema") {
  867. auto schema2 = add_property(schema, "object", {"new", PropertyType::Int});
  868. FAILED_RENAME(schema, schema2,
  869. "Cannot rename property 'object.nonexistent' because it does not exist.",
  870. {"object", "nonexistent", "new"});
  871. }
  872. auto rename_value = [](Schema schema) {
  873. schema.find("object")->property_for_name("value")->name = "new";
  874. return schema;
  875. };
  876. SECTION("property does not exist in new schema") {
  877. FAILED_RENAME(schema, rename_value(schema),
  878. "Renamed property 'object.nonexistent' does not exist.",
  879. {"object", "value", "nonexistent"});
  880. }
  881. SECTION("source propety still exists in the new schema") {
  882. auto schema2 = add_property(schema, "object",
  883. {"new", PropertyType::Int});
  884. FAILED_RENAME(schema, schema2,
  885. "Cannot rename property 'object.value' to 'new' because the source property still exists.",
  886. {"object", "value", "new"});
  887. }
  888. SECTION("different type") {
  889. auto schema2 = rename_value(set_type(schema, "object", "value", PropertyType::Date));
  890. FAILED_RENAME(schema, schema2,
  891. "Cannot rename property 'object.value' to 'new' because it would change from type 'int' to 'date'.",
  892. {"object", "value", "new"});
  893. }
  894. SECTION("different link targets") {
  895. Schema schema = {
  896. {"target", {
  897. {"value", PropertyType::Int},
  898. }},
  899. {"origin", {
  900. {"link", PropertyType::Object|PropertyType::Nullable, "target"},
  901. }},
  902. };
  903. auto schema2 = set_target(schema, "origin", "link", "origin");
  904. schema2.find("origin")->property_for_name("link")->name = "new";
  905. FAILED_RENAME(schema, schema2,
  906. "Cannot rename property 'origin.link' to 'new' because it would change from type '<target>' to '<origin>'.",
  907. {"origin", "link", "new"});
  908. }
  909. SECTION("different linklist targets") {
  910. Schema schema = {
  911. {"target", {
  912. {"value", PropertyType::Int},
  913. }},
  914. {"origin", {
  915. {"link", PropertyType::Array|PropertyType::Object, "target"},
  916. }},
  917. };
  918. auto schema2 = set_target(schema, "origin", "link", "origin");
  919. schema2.find("origin")->property_for_name("link")->name = "new";
  920. FAILED_RENAME(schema, schema2,
  921. "Cannot rename property 'origin.link' to 'new' because it would change from type 'array<target>' to 'array<origin>'.",
  922. {"origin", "link", "new"});
  923. }
  924. SECTION("make required") {
  925. schema = set_optional(schema, "object", "value", true);
  926. auto schema2 = rename_value(set_optional(schema, "object", "value", false));
  927. FAILED_RENAME(schema, schema2,
  928. "Cannot rename property 'object.value' to 'new' because it would change from optional to required.",
  929. {"object", "value", "new"});
  930. }
  931. auto init = [&](Schema const& old_schema) {
  932. realm->update_schema(old_schema, 1);
  933. realm->begin_transaction();
  934. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  935. table->create_object().set_all(10);
  936. realm->commit_transaction();
  937. };
  938. #define SUCCESSFUL_RENAME(old_schema, new_schema, ...) do { \
  939. init(old_schema); \
  940. REQUIRE_NOTHROW(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__}))); \
  941. REQUIRE(realm->schema() == new_schema); \
  942. VERIFY_SCHEMA(*realm, false); \
  943. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); \
  944. auto key = table->get_column_keys()[0]; \
  945. if (table->get_column_attr(key).test(col_attr_Nullable)) \
  946. REQUIRE(table->begin()->get<util::Optional<int64_t>>(key) == 10); \
  947. else \
  948. REQUIRE(table->begin()->get<int64_t>(key) == 10); \
  949. } while (false)
  950. SECTION("basic valid rename") {
  951. auto schema2 = rename_value(schema);
  952. SUCCESSFUL_RENAME(schema, schema2,
  953. {"object", "value", "new"});
  954. }
  955. SECTION("chained rename") {
  956. auto schema2 = rename_value(schema);
  957. SUCCESSFUL_RENAME(schema, schema2,
  958. {"object", "value", "a"},
  959. {"object", "a", "b"},
  960. {"object", "b", "new"});
  961. }
  962. SECTION("old is pk, new is not") {
  963. auto schema2 = rename_value(schema);
  964. schema = set_primary_key(schema, "object", "value");
  965. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  966. }
  967. SECTION("new is pk, old is not") {
  968. auto schema2 = set_primary_key(rename_value(schema), "object", "new");
  969. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  970. }
  971. SECTION("both are pk") {
  972. schema = set_primary_key(schema, "object", "value");
  973. auto schema2 = set_primary_key(rename_value(schema), "object", "new");
  974. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  975. }
  976. SECTION("make optional") {
  977. auto schema2 = rename_value(set_optional(schema, "object", "value", true));
  978. SUCCESSFUL_RENAME(schema, schema2,
  979. {"object", "value", "new"});
  980. }
  981. SECTION("add index") {
  982. auto schema2 = rename_value(set_indexed(schema, "object", "value", true));
  983. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  984. }
  985. SECTION("remove index") {
  986. auto schema2 = rename_value(schema);
  987. schema = set_indexed(schema, "object", "value", true);
  988. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  989. }
  990. }
  991. }
  992. TEST_CASE("migration: Immutable") {
  993. TestFile config;
  994. auto realm_with_schema = [&](Schema schema) {
  995. {
  996. auto realm = Realm::get_shared_realm(config);
  997. realm->update_schema(std::move(schema));
  998. }
  999. config.schema_mode = SchemaMode::Immutable;
  1000. return Realm::get_shared_realm(config);
  1001. };
  1002. SECTION("allowed schema mismatches") {
  1003. SECTION("index") {
  1004. auto realm = realm_with_schema({
  1005. {"object", {
  1006. {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1007. {"unindexed", PropertyType::Int},
  1008. }},
  1009. });
  1010. Schema schema = {
  1011. {"object", {
  1012. {"indexed", PropertyType::Int},
  1013. {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1014. }},
  1015. };
  1016. REQUIRE_NOTHROW(realm->update_schema(schema));
  1017. REQUIRE(realm->schema() == schema);
  1018. }
  1019. SECTION("extra tables") {
  1020. auto realm = realm_with_schema({
  1021. {"object", {
  1022. {"value", PropertyType::Int},
  1023. }},
  1024. {"object 2", {
  1025. {"value", PropertyType::Int},
  1026. }},
  1027. });
  1028. Schema schema = {
  1029. {"object", {
  1030. {"value", PropertyType::Int},
  1031. }},
  1032. };
  1033. REQUIRE_NOTHROW(realm->update_schema(schema));
  1034. }
  1035. SECTION("missing tables") {
  1036. auto realm = realm_with_schema({
  1037. {"object", {
  1038. {"value", PropertyType::Int},
  1039. }},
  1040. });
  1041. Schema schema = {
  1042. {"object", {
  1043. {"value", PropertyType::Int},
  1044. }},
  1045. {"second object", {
  1046. {"value", PropertyType::Int},
  1047. }},
  1048. };
  1049. REQUIRE_NOTHROW(realm->update_schema(schema));
  1050. REQUIRE(realm->schema() == schema);
  1051. auto object_schema = realm->schema().find("object");
  1052. REQUIRE(object_schema->persisted_properties.size() == 1);
  1053. REQUIRE(object_schema->persisted_properties[0].column_key);
  1054. object_schema = realm->schema().find("second object");
  1055. REQUIRE(object_schema->persisted_properties.size() == 1);
  1056. REQUIRE(!object_schema->persisted_properties[0].column_key);
  1057. }
  1058. SECTION("extra columns in table") {
  1059. auto realm = realm_with_schema({
  1060. {"object", {
  1061. {"value", PropertyType::Int},
  1062. {"value 2", PropertyType::Int},
  1063. }},
  1064. });
  1065. Schema schema = {
  1066. {"object", {
  1067. {"value", PropertyType::Int},
  1068. }},
  1069. };
  1070. REQUIRE_NOTHROW(realm->update_schema(schema));
  1071. }
  1072. }
  1073. SECTION("disallowed mismatches") {
  1074. SECTION("missing columns in table") {
  1075. auto realm = realm_with_schema({
  1076. {"object", {
  1077. {"value", PropertyType::Int},
  1078. }},
  1079. });
  1080. Schema schema = {
  1081. {"object", {
  1082. {"value", PropertyType::Int},
  1083. {"value 2", PropertyType::Int},
  1084. }},
  1085. };
  1086. REQUIRE_THROWS(realm->update_schema(schema));
  1087. }
  1088. SECTION("bump schema version") {
  1089. Schema schema = {
  1090. {"object", {
  1091. {"value", PropertyType::Int},
  1092. }},
  1093. };
  1094. auto realm = realm_with_schema(schema);
  1095. REQUIRE_THROWS(realm->update_schema(schema, 1));
  1096. }
  1097. }
  1098. }
  1099. TEST_CASE("migration: ReadOnly") {
  1100. TestFile config;
  1101. auto realm_with_schema = [&](Schema schema) {
  1102. {
  1103. auto realm = Realm::get_shared_realm(config);
  1104. realm->update_schema(std::move(schema));
  1105. }
  1106. config.schema_mode = SchemaMode::ReadOnlyAlternative;
  1107. return Realm::get_shared_realm(config);
  1108. };
  1109. SECTION("allowed schema mismatches") {
  1110. SECTION("index") {
  1111. auto realm = realm_with_schema({
  1112. {"object", {
  1113. {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1114. {"unindexed", PropertyType::Int},
  1115. }},
  1116. });
  1117. Schema schema = {
  1118. {"object", {
  1119. {"indexed", PropertyType::Int},
  1120. {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1121. }},
  1122. };
  1123. REQUIRE_NOTHROW(realm->update_schema(schema));
  1124. REQUIRE(realm->schema() == schema);
  1125. }
  1126. SECTION("extra tables") {
  1127. auto realm = realm_with_schema({
  1128. {"object", {
  1129. {"value", PropertyType::Int},
  1130. }},
  1131. {"object 2", {
  1132. {"value", PropertyType::Int},
  1133. }},
  1134. });
  1135. Schema schema = {
  1136. {"object", {
  1137. {"value", PropertyType::Int},
  1138. }},
  1139. };
  1140. REQUIRE_NOTHROW(realm->update_schema(schema));
  1141. }
  1142. SECTION("extra columns in table") {
  1143. auto realm = realm_with_schema({
  1144. {"object", {
  1145. {"value", PropertyType::Int},
  1146. {"value 2", PropertyType::Int},
  1147. }},
  1148. });
  1149. Schema schema = {
  1150. {"object", {
  1151. {"value", PropertyType::Int},
  1152. }},
  1153. };
  1154. REQUIRE_NOTHROW(realm->update_schema(schema));
  1155. }
  1156. SECTION("missing tables") {
  1157. auto realm = realm_with_schema({
  1158. {"object", {
  1159. {"value", PropertyType::Int},
  1160. }},
  1161. });
  1162. Schema schema = {
  1163. {"object", {
  1164. {"value", PropertyType::Int},
  1165. }},
  1166. {"second object", {
  1167. {"value", PropertyType::Int},
  1168. }},
  1169. };
  1170. REQUIRE_NOTHROW(realm->update_schema(schema));
  1171. }
  1172. SECTION("bump schema version") {
  1173. Schema schema = {
  1174. {"object", {
  1175. {"value", PropertyType::Int},
  1176. }},
  1177. };
  1178. auto realm = realm_with_schema(schema);
  1179. REQUIRE_NOTHROW(realm->update_schema(schema, 1));
  1180. }
  1181. }
  1182. SECTION("disallowed mismatches") {
  1183. SECTION("missing columns in table") {
  1184. auto realm = realm_with_schema({
  1185. {"object", {
  1186. {"value", PropertyType::Int},
  1187. }},
  1188. });
  1189. Schema schema = {
  1190. {"object", {
  1191. {"value", PropertyType::Int},
  1192. {"value 2", PropertyType::Int},
  1193. }},
  1194. };
  1195. REQUIRE_THROWS(realm->update_schema(schema));
  1196. }
  1197. }
  1198. }
  1199. TEST_CASE("migration: ResetFile") {
  1200. TestFile config;
  1201. config.schema_mode = SchemaMode::ResetFile;
  1202. Schema schema = {
  1203. {"object", {
  1204. {"value", PropertyType::Int},
  1205. }},
  1206. {"object 2", {
  1207. {"value", PropertyType::Int},
  1208. }},
  1209. };
  1210. // To verify that the file has actually be deleted and recreated, on
  1211. // non-Windows we need to hold an open file handle to the old file to force
  1212. // using a new inode, but on Windows we *can't*
  1213. #ifdef _WIN32
  1214. auto get_fileid = [&] {
  1215. // this is wrong for non-ascii but it's what core does
  1216. std::wstring ws(config.path.begin(), config.path.end());
  1217. HANDLE handle = CreateFile2(ws.c_str(), GENERIC_READ,
  1218. FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING,
  1219. nullptr);
  1220. REQUIRE(handle != INVALID_HANDLE_VALUE);
  1221. auto close = util::make_scope_exit([=]() noexcept { CloseHandle(handle); });
  1222. BY_HANDLE_FILE_INFORMATION info{};
  1223. REQUIRE(GetFileInformationByHandle(handle, &info));
  1224. return (DWORDLONG)info.nFileIndexHigh + (DWORDLONG)info.nFileIndexLow;
  1225. };
  1226. #else
  1227. auto get_fileid = [&] {
  1228. util::File::UniqueID id;
  1229. util::File::get_unique_id(config.path, id);
  1230. return id.inode;
  1231. };
  1232. File holder(config.path, File::mode_Write);
  1233. #endif
  1234. {
  1235. auto realm = Realm::get_shared_realm(config);
  1236. auto ino = get_fileid();
  1237. realm->update_schema(schema);
  1238. REQUIRE(ino == get_fileid());
  1239. realm->begin_transaction();
  1240. ObjectStore::table_for_object_type(realm->read_group(), "object")->create_object();
  1241. realm->commit_transaction();
  1242. }
  1243. auto realm = Realm::get_shared_realm(config);
  1244. auto ino = get_fileid();
  1245. SECTION("file is reset when schema version increases") {
  1246. realm->update_schema(schema, 1);
  1247. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0);
  1248. REQUIRE(ino != get_fileid());
  1249. }
  1250. SECTION("file is reset when an existing table is modified") {
  1251. realm->update_schema(add_property(schema, "object",
  1252. {"value 2", PropertyType::Int}));
  1253. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0);
  1254. REQUIRE(ino != get_fileid());
  1255. }
  1256. SECTION("file is not reset when adding a new table") {
  1257. realm->update_schema(add_table(schema, {"object 3", {
  1258. {"value", PropertyType::Int},
  1259. }}));
  1260. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1261. REQUIRE(realm->schema().size() == 3);
  1262. REQUIRE(ino == get_fileid());
  1263. }
  1264. SECTION("file is not reset when removing a table") {
  1265. realm->update_schema(remove_table(schema, "object 2"));
  1266. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1267. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2"));
  1268. REQUIRE(realm->schema().size() == 1);
  1269. REQUIRE(ino == get_fileid());
  1270. }
  1271. SECTION("file is not reset when adding an index") {
  1272. realm->update_schema(set_indexed(schema, "object", "value", true));
  1273. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1274. REQUIRE(ino == get_fileid());
  1275. }
  1276. SECTION("file is not reset when removing an index") {
  1277. realm->update_schema(set_indexed(schema, "object", "value", true));
  1278. realm->update_schema(schema);
  1279. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1280. REQUIRE(ino == get_fileid());
  1281. }
  1282. }
  1283. TEST_CASE("migration: Additive") {
  1284. Schema schema = {
  1285. {"object", {
  1286. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1287. {"value 2", PropertyType::Int|PropertyType::Nullable},
  1288. }},
  1289. };
  1290. TestFile config;
  1291. config.schema_mode = SchemaMode::Additive;
  1292. config.schema = schema;
  1293. auto realm = Realm::get_shared_realm(config);
  1294. realm->update_schema(schema);
  1295. SECTION("can add new properties to existing tables") {
  1296. REQUIRE_NOTHROW(realm->update_schema(add_property(schema, "object",
  1297. {"value 3", PropertyType::Int})));
  1298. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 3);
  1299. }
  1300. SECTION("can add new tables") {
  1301. REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
  1302. {"value", PropertyType::Int},
  1303. }})));
  1304. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object"));
  1305. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2"));
  1306. }
  1307. SECTION("indexes are updated when schema version is bumped") {
  1308. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1309. auto col_keys = table->get_column_keys();
  1310. REQUIRE(table->has_search_index(col_keys[0]));
  1311. REQUIRE(!table->has_search_index(col_keys[1]));
  1312. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false), 1));
  1313. REQUIRE(!table->has_search_index(col_keys[0]));
  1314. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true), 2));
  1315. REQUIRE(table->has_search_index(col_keys[1]));
  1316. }
  1317. SECTION("indexes are not updated when schema version is not bumped") {
  1318. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1319. auto col_keys = table->get_column_keys();
  1320. REQUIRE(table->has_search_index(col_keys[0]));
  1321. REQUIRE(!table->has_search_index(col_keys[1]));
  1322. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false)));
  1323. REQUIRE(table->has_search_index(col_keys[0]));
  1324. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true)));
  1325. REQUIRE(!table->has_search_index(col_keys[1]));
  1326. }
  1327. SECTION("can remove properties from existing tables, but column is not removed") {
  1328. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1329. REQUIRE_NOTHROW(realm->update_schema(remove_property(schema, "object", "value")));
  1330. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 2);
  1331. auto const& properties = realm->schema().find("object")->persisted_properties;
  1332. REQUIRE(properties.size() == 1);
  1333. auto col_keys = table->get_column_keys();
  1334. REQUIRE(col_keys.size() == 2);
  1335. REQUIRE(properties[0].column_key == col_keys[1]);
  1336. }
  1337. SECTION("cannot change existing property types") {
  1338. REQUIRE_THROWS(realm->update_schema(set_type(schema, "object", "value", PropertyType::Float)));
  1339. }
  1340. SECTION("cannot change existing property nullability") {
  1341. REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value", true)));
  1342. REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value 2", false)));
  1343. }
  1344. SECTION("cannot change existing link targets") {
  1345. REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
  1346. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  1347. }})));
  1348. REQUIRE_THROWS(realm->update_schema(set_target(realm->schema(), "object 2", "link", "object 2")));
  1349. }
  1350. SECTION("cannot change primary keys") {
  1351. REQUIRE_THROWS(realm->update_schema(set_primary_key(schema, "object", "value")));
  1352. REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
  1353. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  1354. }})));
  1355. REQUIRE_THROWS(realm->update_schema(set_primary_key(realm->schema(), "object 2", "")));
  1356. }
  1357. SECTION("schema version is allowed to go down") {
  1358. REQUIRE_NOTHROW(realm->update_schema(schema, 1));
  1359. REQUIRE(realm->schema_version() == 1);
  1360. REQUIRE_NOTHROW(realm->update_schema(schema, 0));
  1361. REQUIRE(realm->schema_version() == 1);
  1362. }
  1363. SECTION("migration function is not used") {
  1364. REQUIRE_NOTHROW(realm->update_schema(schema, 1,
  1365. [&](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); }));
  1366. }
  1367. SECTION("add new columns from different SG") {
  1368. auto realm2 = Realm::get_shared_realm(config);
  1369. auto& group = realm2->read_group();
  1370. realm2->begin_transaction();
  1371. auto table = ObjectStore::table_for_object_type(group, "object");
  1372. auto col_keys = table->get_column_keys();
  1373. table->add_column(type_Int, "new column");
  1374. realm2->commit_transaction();
  1375. REQUIRE_NOTHROW(realm->refresh());
  1376. REQUIRE(realm->schema() == schema);
  1377. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1378. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1379. }
  1380. SECTION("opening new Realms uses the correct schema after an external change") {
  1381. auto realm2 = Realm::get_shared_realm(config);
  1382. auto& group = realm2->read_group();
  1383. realm2->begin_transaction();
  1384. auto table = ObjectStore::table_for_object_type(group, "object");
  1385. auto col_keys = table->get_column_keys();
  1386. table->add_column(type_Double, "newcol");
  1387. realm2->commit_transaction();
  1388. REQUIRE_NOTHROW(realm->refresh());
  1389. REQUIRE(realm->schema() == schema);
  1390. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1391. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1392. // Gets the schema from the RealmCoordinator
  1393. auto realm3 = Realm::get_shared_realm(config);
  1394. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1395. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1396. // Close and re-open the file entirely so that the coordinator is recreated
  1397. realm.reset();
  1398. realm2.reset();
  1399. realm3.reset();
  1400. realm = Realm::get_shared_realm(config);
  1401. REQUIRE(realm->schema() == schema);
  1402. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1403. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1404. }
  1405. SECTION("can have different subsets of columns in different Realm instances") {
  1406. auto config2 = config;
  1407. config2.schema = add_property(schema, "object",
  1408. {"value 3", PropertyType::Int});
  1409. auto config3 = config;
  1410. config3.schema = remove_property(schema, "object", "value 2");
  1411. auto config4 = config;
  1412. config4.schema = util::none;
  1413. auto realm2 = Realm::get_shared_realm(config2);
  1414. auto realm3 = Realm::get_shared_realm(config3);
  1415. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
  1416. REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3);
  1417. REQUIRE(realm3->schema().find("object")->persisted_properties.size() == 1);
  1418. realm->refresh();
  1419. realm2->refresh();
  1420. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
  1421. REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3);
  1422. // No schema specified; should see all of them
  1423. auto realm4 = Realm::get_shared_realm(config4);
  1424. REQUIRE(realm4->schema().find("object")->persisted_properties.size() == 3);
  1425. }
  1426. SECTION("updating a schema to include already-present column") {
  1427. auto config2 = config;
  1428. config2.schema = add_property(schema, "object",
  1429. {"value 3", PropertyType::Int});
  1430. auto realm2 = Realm::get_shared_realm(config2);
  1431. auto& properties2 = realm2->schema().find("object")->persisted_properties;
  1432. REQUIRE_NOTHROW(realm->update_schema(*config2.schema));
  1433. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 3);
  1434. auto& properties = realm->schema().find("object")->persisted_properties;
  1435. REQUIRE(properties[0].column_key == properties2[0].column_key);
  1436. REQUIRE(properties[1].column_key == properties2[1].column_key);
  1437. REQUIRE(properties[2].column_key == properties2[2].column_key);
  1438. }
  1439. SECTION("increasing schema version without modifying schema properly leaves the schema untouched") {
  1440. TestFile config1;
  1441. config1.schema = schema;
  1442. config1.schema_mode = SchemaMode::Additive;
  1443. config1.schema_version = 0;
  1444. auto realm1 = Realm::get_shared_realm(config1);
  1445. REQUIRE(realm1->schema().size() == 1);
  1446. Schema schema1 = realm1->schema();
  1447. realm1->close();
  1448. auto config2 = config1;
  1449. config2.schema_version = 1;
  1450. auto realm2 = Realm::get_shared_realm(config2);
  1451. REQUIRE(realm2->schema() == schema1);
  1452. }
  1453. SECTION("invalid schema update leaves the schema untouched") {
  1454. auto config2 = config;
  1455. config2.schema = add_property(schema, "object", {"value 3", PropertyType::Int});
  1456. auto realm2 = Realm::get_shared_realm(config2);
  1457. REQUIRE_THROWS(realm->update_schema(add_property(schema, "object", {"value 3", PropertyType::Float})));
  1458. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
  1459. }
  1460. SECTION("update_schema() does not begin a write transaction when extra columns are present") {
  1461. realm->begin_transaction();
  1462. auto realm2 = Realm::get_shared_realm(config);
  1463. // will deadlock if it tries to start a write transaction
  1464. realm2->update_schema(remove_property(schema, "object", "value"));
  1465. }
  1466. SECTION("update_schema() does not begin a write transaction when indexes are changed without bumping schema version") {
  1467. realm->begin_transaction();
  1468. auto realm2 = Realm::get_shared_realm(config);
  1469. // will deadlock if it tries to start a write transaction
  1470. realm->update_schema(set_indexed(schema, "object", "value 2", true));
  1471. }
  1472. SECTION("update_schema() does not begin a write transaction for invalid schema changes") {
  1473. realm->begin_transaction();
  1474. auto realm2 = Realm::get_shared_realm(config);
  1475. auto new_schema = add_property(remove_property(schema, "object", "value"),
  1476. "object", {"value", PropertyType::Float});
  1477. // will deadlock if it tries to start a write transaction
  1478. REQUIRE_THROWS(realm2->update_schema(new_schema));
  1479. }
  1480. }
  1481. TEST_CASE("migration: Manual") {
  1482. TestFile config;
  1483. config.schema_mode = SchemaMode::Manual;
  1484. auto realm = Realm::get_shared_realm(config);
  1485. Schema schema = {
  1486. {"object", {
  1487. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  1488. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1489. {"optional", PropertyType::Int|PropertyType::Nullable},
  1490. }},
  1491. {"link origin", {
  1492. {"not a pk", PropertyType::Int},
  1493. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  1494. {"array", PropertyType::Array|PropertyType::Object, "object"},
  1495. }}
  1496. };
  1497. realm->update_schema(schema);
  1498. auto col_keys = realm->read_group().get_table("class_object")->get_column_keys();
  1499. #define REQUIRE_MIGRATION(schema, migration) do { \
  1500. Schema new_schema = (schema); \
  1501. REQUIRE_THROWS(realm->update_schema(new_schema)); \
  1502. REQUIRE(realm->schema_version() == 0); \
  1503. REQUIRE_THROWS(realm->update_schema(new_schema, 1, [](SharedRealm, SharedRealm, Schema&){})); \
  1504. REQUIRE(realm->schema_version() == 0); \
  1505. REQUIRE_NOTHROW(realm->update_schema(new_schema, 1, migration)); \
  1506. REQUIRE(realm->schema_version() == 1); \
  1507. } while (false)
  1508. SECTION("add new table") {
  1509. REQUIRE_MIGRATION(add_table(schema, {"new table", {
  1510. {"value", PropertyType::Int},
  1511. }}), [](SharedRealm, SharedRealm realm, Schema&) {
  1512. realm->read_group().add_table("class_new table")->add_column(type_Int, "value");
  1513. });
  1514. }
  1515. SECTION("add property to table") {
  1516. REQUIRE_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}),
  1517. [&](SharedRealm, SharedRealm realm, Schema&) {
  1518. get_table(realm, "object")->add_column(type_Int, "new");
  1519. });
  1520. }
  1521. SECTION("remove property from table") {
  1522. REQUIRE_MIGRATION(remove_property(schema, "object", "value"),
  1523. [&](SharedRealm, SharedRealm realm, Schema&) {
  1524. get_table(realm, "object")->remove_column(col_keys[1]);
  1525. });
  1526. }
  1527. SECTION("add primary key to table") {
  1528. REQUIRE_MIGRATION(set_primary_key(schema, "link origin", "not a pk"),
  1529. [&](SharedRealm, SharedRealm realm, Schema&) {
  1530. ObjectStore::set_primary_key_for_object(realm->read_group(), "link origin", "not a pk");
  1531. auto table = get_table(realm, "link origin");
  1532. table->add_search_index(table->get_column_key("not a pk"));
  1533. });
  1534. }
  1535. SECTION("remove primary key from table") {
  1536. REQUIRE_MIGRATION(set_primary_key(schema, "object", ""),
  1537. [&](SharedRealm, SharedRealm realm, Schema&) {
  1538. ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "");
  1539. get_table(realm, "object")->remove_search_index(col_keys[0]);
  1540. });
  1541. }
  1542. SECTION("change primary key") {
  1543. REQUIRE_MIGRATION(set_primary_key(schema, "object", "value"),
  1544. [&](SharedRealm, SharedRealm realm, Schema&) {
  1545. ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "value");
  1546. auto table = get_table(realm, "object");
  1547. table->remove_search_index(col_keys[0]);
  1548. table->add_search_index(col_keys[1]);
  1549. });
  1550. }
  1551. SECTION("change property type") {
  1552. REQUIRE_MIGRATION(set_type(schema, "object", "value", PropertyType::Date),
  1553. [&](SharedRealm, SharedRealm realm, Schema&) {
  1554. auto table = get_table(realm, "object");
  1555. table->remove_column(col_keys[1]);
  1556. auto col = table->add_column(type_Timestamp, "value");
  1557. table->add_search_index(col);
  1558. });
  1559. }
  1560. SECTION("change link target") {
  1561. REQUIRE_MIGRATION(set_target(schema, "link origin", "object", "link origin"),
  1562. [&](SharedRealm, SharedRealm realm, Schema&) {
  1563. auto table = get_table(realm, "link origin");
  1564. table->remove_column(table->get_column_keys()[1]);
  1565. table->add_column_link(type_Link, "object", *table);
  1566. });
  1567. }
  1568. SECTION("change linklist target") {
  1569. REQUIRE_MIGRATION(set_target(schema, "link origin", "array", "link origin"),
  1570. [&](SharedRealm, SharedRealm realm, Schema&) {
  1571. auto table = get_table(realm, "link origin");
  1572. table->remove_column(table->get_column_keys()[2]);
  1573. table->add_column_link(type_LinkList, "array", *table);
  1574. });
  1575. }
  1576. SECTION("make property optional") {
  1577. REQUIRE_MIGRATION(set_optional(schema, "object", "value", true),
  1578. [&](SharedRealm, SharedRealm realm, Schema&) {
  1579. auto table = get_table(realm, "object");
  1580. table->remove_column(col_keys[1]);
  1581. auto col = table->add_column(type_Int, "value", true);
  1582. table->add_search_index(col);
  1583. });
  1584. }
  1585. SECTION("make property required") {
  1586. REQUIRE_MIGRATION(set_optional(schema, "object", "optional", false),
  1587. [&](SharedRealm, SharedRealm realm, Schema&) {
  1588. auto table = get_table(realm, "object");
  1589. table->remove_column(col_keys[2]);
  1590. table->add_column(type_Int, "optional", false);
  1591. });
  1592. }
  1593. SECTION("add index") {
  1594. REQUIRE_MIGRATION(set_indexed(schema, "object", "optional", true),
  1595. [&](SharedRealm, SharedRealm realm, Schema&) {
  1596. get_table(realm, "object")->add_search_index(col_keys[2]);
  1597. });
  1598. }
  1599. SECTION("remove index") {
  1600. REQUIRE_MIGRATION(set_indexed(schema, "object", "value", false),
  1601. [&](SharedRealm, SharedRealm realm, Schema&) {
  1602. get_table(realm, "object")->remove_search_index(col_keys[1]);
  1603. });
  1604. }
  1605. SECTION("reorder properties") {
  1606. auto schema2 = schema;
  1607. auto& properties = schema2.find("object")->persisted_properties;
  1608. std::swap(properties[0], properties[1]);
  1609. REQUIRE_NOTHROW(realm->update_schema(schema2));
  1610. }
  1611. SECTION("cannot lower schema version") {
  1612. REQUIRE_NOTHROW(realm->update_schema(schema, 1, [](SharedRealm, SharedRealm, Schema&){}));
  1613. REQUIRE(realm->schema_version() == 1);
  1614. REQUIRE_THROWS(realm->update_schema(schema, 0, [](SharedRealm, SharedRealm, Schema&){}));
  1615. REQUIRE(realm->schema_version() == 1);
  1616. }
  1617. SECTION("update_schema() does not begin a write transaction when schema version is unchanged") {
  1618. realm->begin_transaction();
  1619. auto realm2 = Realm::get_shared_realm(config);
  1620. // will deadlock if it tries to start a write transaction
  1621. REQUIRE_NOTHROW(realm2->update_schema(schema));
  1622. REQUIRE_THROWS(realm2->update_schema(remove_property(schema, "object", "value")));
  1623. }
  1624. SECTION("null migration callback should throw SchemaMismatchException") {
  1625. Schema new_schema = remove_property(schema, "object", "value");
  1626. REQUIRE_THROWS_AS(realm->update_schema(new_schema, 1, nullptr), SchemaMismatchException);
  1627. }
  1628. }