migrations.cpp 76 KB

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