migrations.cpp 85 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059
  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. {"int pk", {
  637. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  638. {"value", PropertyType::Int},
  639. }},
  640. {"string pk", {
  641. {"pk", PropertyType::String, Property::IsPrimary{true}},
  642. {"value", PropertyType::Int},
  643. }},
  644. };
  645. InMemoryTestFile config;
  646. config.schema_mode = SchemaMode::Automatic;
  647. config.schema = schema;
  648. auto realm = Realm::get_shared_realm(config);
  649. CppContext ctx(realm);
  650. util::Any values = AnyDict{
  651. {"pk", INT64_C(1)},
  652. {"bool", true},
  653. {"int", INT64_C(5)},
  654. {"float", 2.2f},
  655. {"double", 3.3},
  656. {"string", "hello"s},
  657. {"data", "olleh"s},
  658. {"date", Timestamp(10, 20)},
  659. {"object", AnyDict{{"value", INT64_C(10)}}},
  660. {"array", AnyVector{AnyDict{{"value", INT64_C(20)}}}},
  661. };
  662. realm->begin_transaction();
  663. Object::create(ctx, realm, *realm->schema().find("all types"), values);
  664. realm->commit_transaction();
  665. SECTION("read values from old realm") {
  666. Schema schema{
  667. {"all types", {
  668. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  669. }},
  670. };
  671. realm->update_schema(schema, 2, [](auto old_realm, auto new_realm, Schema&) {
  672. CppContext ctx(old_realm);
  673. Object obj = Object::get_for_primary_key(ctx, old_realm, "all types",
  674. util::Any(INT64_C(1)));
  675. REQUIRE(obj.is_valid());
  676. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true);
  677. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5);
  678. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f);
  679. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3);
  680. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello");
  681. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh");
  682. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20));
  683. auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object"));
  684. REQUIRE(link.is_valid());
  685. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10);
  686. auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array"));
  687. REQUIRE(list.size() == 1);
  688. CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array"));
  689. link = any_cast<Object>(list.get(list_ctx, 0));
  690. REQUIRE(link.is_valid());
  691. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20);
  692. CppContext ctx2(new_realm);
  693. obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  694. util::Any(INT64_C(1)));
  695. REQUIRE(obj.is_valid());
  696. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool"));
  697. });
  698. }
  699. SECTION("cannot mutate old realm") {
  700. realm->update_schema(schema, 2, [](auto old_realm, auto, Schema&) {
  701. CppContext ctx(old_realm);
  702. Object obj = Object::get_for_primary_key(ctx, old_realm, "all types",
  703. util::Any(INT64_C(1)));
  704. REQUIRE(obj.is_valid());
  705. REQUIRE_THROWS(obj.set_property_value(ctx, "bool", util::Any(false)));
  706. REQUIRE_THROWS(old_realm->begin_transaction());
  707. });
  708. }
  709. SECTION("cannot read values for removed properties from new realm") {
  710. Schema schema{
  711. {"all types", {
  712. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  713. }},
  714. };
  715. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  716. CppContext ctx(new_realm);
  717. Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  718. util::Any(INT64_C(1)));
  719. REQUIRE(obj.is_valid());
  720. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "bool"));
  721. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "object"));
  722. REQUIRE_THROWS(obj.get_property_value<util::Any>(ctx, "array"));
  723. });
  724. }
  725. SECTION("read values from new object") {
  726. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  727. CppContext ctx(new_realm);
  728. Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  729. util::Any(INT64_C(1)));
  730. REQUIRE(obj.is_valid());
  731. auto link = any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object"));
  732. REQUIRE(link.is_valid());
  733. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(ctx, "value")) == 10);
  734. auto list = any_cast<List>(obj.get_property_value<util::Any>(ctx, "array"));
  735. REQUIRE(list.size() == 1);
  736. CppContext list_ctx(ctx, *obj.get_object_schema().property_for_name("array"));
  737. link = any_cast<Object>(list.get(list_ctx, 0));
  738. REQUIRE(link.is_valid());
  739. REQUIRE(any_cast<int64_t>(link.get_property_value<util::Any>(list_ctx, "value")) == 20);
  740. });
  741. }
  742. SECTION("read and write values in new object") {
  743. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  744. CppContext ctx(new_realm);
  745. Object obj = Object::get_for_primary_key(ctx, new_realm, "all types",
  746. util::Any(INT64_C(1)));
  747. REQUIRE(obj.is_valid());
  748. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == true);
  749. obj.set_property_value(ctx, "bool", util::Any(false));
  750. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false);
  751. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 5);
  752. obj.set_property_value(ctx, "int", util::Any(INT64_C(6)));
  753. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "int")) == 6);
  754. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 2.2f);
  755. obj.set_property_value(ctx, "float", util::Any(1.23f));
  756. REQUIRE(any_cast<float>(obj.get_property_value<util::Any>(ctx, "float")) == 1.23f);
  757. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 3.3);
  758. obj.set_property_value(ctx, "double", util::Any(1.23));
  759. REQUIRE(any_cast<double>(obj.get_property_value<util::Any>(ctx, "double")) == 1.23);
  760. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "hello");
  761. obj.set_property_value(ctx, "string", util::Any("abc"s));
  762. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "string")) == "abc");
  763. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "olleh");
  764. obj.set_property_value(ctx, "data", util::Any("abc"s));
  765. REQUIRE(any_cast<std::string>(obj.get_property_value<util::Any>(ctx, "data")) == "abc");
  766. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(10, 20));
  767. obj.set_property_value(ctx, "date", util::Any(Timestamp(1, 2)));
  768. REQUIRE(any_cast<Timestamp>(obj.get_property_value<util::Any>(ctx, "date")) == Timestamp(1, 2));
  769. Object linked_obj(new_realm, "link target", 0);
  770. Object new_obj(new_realm, get_table(new_realm, "link target")->create_object());
  771. auto linking = any_cast<Results>(linked_obj.get_property_value<util::Any>(ctx, "origin"));
  772. REQUIRE(linking.size() == 1);
  773. REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).obj().get_key()
  774. == linked_obj.obj().get_key());
  775. obj.set_property_value(ctx, "object", util::Any(new_obj));
  776. REQUIRE(any_cast<Object>(obj.get_property_value<util::Any>(ctx, "object")).obj().get_key()
  777. == new_obj.obj().get_key());
  778. REQUIRE(linking.size() == 0);
  779. });
  780. }
  781. SECTION("create object in new realm") {
  782. realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) {
  783. REQUIRE(new_realm->is_in_transaction());
  784. CppContext ctx(new_realm);
  785. any_cast<AnyDict&>(values)["pk"] = INT64_C(2);
  786. Object obj = Object::create(ctx, new_realm, "all types", values);
  787. REQUIRE(get_table(new_realm, "all types")->size() == 2);
  788. REQUIRE(get_table(new_realm, "link target")->size() == 2);
  789. REQUIRE(get_table(new_realm, "array target")->size() == 2);
  790. REQUIRE(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "pk")) == 2);
  791. });
  792. }
  793. SECTION("upsert in new realm") {
  794. realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) {
  795. REQUIRE(new_realm->is_in_transaction());
  796. CppContext ctx(new_realm);
  797. any_cast<AnyDict&>(values)["bool"] = false;
  798. Object obj = Object::create(ctx, new_realm, "all types", values, CreatePolicy::UpdateAll);
  799. REQUIRE(get_table(new_realm, "all types")->size() == 1);
  800. REQUIRE(get_table(new_realm, "link target")->size() == 2);
  801. REQUIRE(get_table(new_realm, "array target")->size() == 2);
  802. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false);
  803. });
  804. }
  805. SECTION("upsert in new realm after modifying primary key") {
  806. realm->update_schema(schema, 2, [&values](auto, auto new_realm, Schema&) {
  807. get_table(new_realm, "all types")->set_primary_key_column(ColKey());
  808. REQUIRE(new_realm->is_in_transaction());
  809. CppContext ctx(new_realm);
  810. any_cast<AnyDict&>(values)["bool"] = false;
  811. Object obj = Object::create(ctx, new_realm, "all types", values, CreatePolicy::UpdateAll);
  812. REQUIRE(get_table(new_realm, "all types")->size() == 1);
  813. REQUIRE(get_table(new_realm, "link target")->size() == 2);
  814. REQUIRE(get_table(new_realm, "array target")->size() == 2);
  815. REQUIRE(any_cast<bool>(obj.get_property_value<util::Any>(ctx, "bool")) == false);
  816. });
  817. }
  818. SECTION("change primary key property type") {
  819. schema = set_type(schema, "all types", "pk", PropertyType::String);
  820. realm->update_schema(schema, 2, [](auto, auto new_realm, auto&) {
  821. Object obj(new_realm, "all types", 0);
  822. CppContext ctx(new_realm);
  823. obj.set_property_value(ctx, "pk", util::Any("1"s));
  824. });
  825. }
  826. SECTION("set primary key to duplicate values in migration") {
  827. auto bad_migration = [&](auto, auto new_realm, Schema&) {
  828. // shoud be able to create a new object with the same PK
  829. REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values));
  830. REQUIRE(get_table(new_realm, "all types")->size() == 2);
  831. // but it'll fail at the end
  832. };
  833. REQUIRE_THROWS_AS(realm->update_schema(schema, 2, bad_migration), DuplicatePrimaryKeyValueException);
  834. REQUIRE(get_table(realm, "all types")->size() == 1);
  835. auto good_migration = [&](auto, auto new_realm, Schema&) {
  836. REQUIRE_NOTHROW(Object::create(ctx, new_realm, "all types", values));
  837. // Change the old object's PK to elminate the duplication
  838. Object old_obj(new_realm, "all types", 0);
  839. CppContext ctx(new_realm);
  840. old_obj.set_property_value(ctx, "pk", util::Any(INT64_C(5)));
  841. };
  842. REQUIRE_NOTHROW(realm->update_schema(schema, 2, good_migration));
  843. REQUIRE(get_table(realm, "all types")->size() == 2);
  844. }
  845. SECTION("modify existing int primary key values in migration") {
  846. // Create several more objects to increase the chance of things
  847. // actually breaking if we're doing invalid things
  848. CppContext ctx(realm);
  849. auto object_schema = realm->schema().find("all types");
  850. realm->begin_transaction();
  851. for (int i = 1; i < 10; ++i) {
  852. any_cast<AnyDict&>(values)["pk"] = INT64_C(1) + i;
  853. any_cast<AnyDict&>(values)["int"] = INT64_C(5) + i;
  854. Object::create(ctx, realm, *object_schema, values);
  855. }
  856. realm->commit_transaction();
  857. // Increase the PK of each object by one in a migration
  858. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  859. CppContext ctx(new_realm);
  860. Results results(new_realm, get_table(new_realm, "all types"));
  861. for (size_t i = 0, count = results.size(); i < count; ++i) {
  862. Object obj(new_realm, results.get<Obj>(i));
  863. util::Any v = 1 + any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "pk"));
  864. obj.set_property_value(ctx, "pk", v);
  865. }
  866. });
  867. // Create a new object with the no-longer-used pk of 1
  868. realm->begin_transaction();
  869. any_cast<AnyDict&>(values)["pk"] = INT64_C(1);
  870. any_cast<AnyDict&>(values)["int"] = INT64_C(4);
  871. object_schema = realm->schema().find("all types");
  872. Object::create(ctx, realm, *object_schema, values);
  873. realm->commit_transaction();
  874. // Verify results
  875. auto table = get_table(realm, "all types");
  876. REQUIRE(table->size() == 11);
  877. REQUIRE(table->get_primary_key_column() == table->get_column_key("pk"));
  878. for (int i = 0; i < 10; ++i) {
  879. auto obj = table->get_object(i);
  880. REQUIRE(obj.get<int64_t>("pk") == i + 2);
  881. REQUIRE(obj.get<int64_t>("int") == i + 5);
  882. }
  883. auto obj = table->get_object(10);
  884. REQUIRE(obj.get<int64_t>("pk") == 1);
  885. REQUIRE(obj.get<int64_t>("int") == 4);
  886. }
  887. SECTION("modify existing string primary key values in migration") {
  888. // Create several objects to increase the chance of things
  889. // actually breaking if we're doing invalid things
  890. CppContext ctx(realm);
  891. auto object_schema = realm->schema().find("string pk");
  892. realm->begin_transaction();
  893. for (int64_t i = 0; i < 10; ++i) {
  894. util::Any values = AnyDict{
  895. {"pk", util::to_string(i)},
  896. {"value", i + 1},
  897. };
  898. Object::create(ctx, realm, *object_schema, values);
  899. }
  900. realm->commit_transaction();
  901. // Increase the PK of each object by one in a migration
  902. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  903. CppContext ctx(new_realm);
  904. Results results(new_realm, get_table(new_realm, "string pk"));
  905. for (size_t i = 0, count = results.size(); i < count; ++i) {
  906. Object obj(new_realm, results.get<Obj>(i));
  907. util::Any v = util::to_string(any_cast<int64_t>(obj.get_property_value<util::Any>(ctx, "value")));
  908. obj.set_property_value(ctx, "pk", v);
  909. }
  910. });
  911. // Create a new object with the no-longer-used pk of 0
  912. realm->begin_transaction();
  913. util::Any values = AnyDict{
  914. {"pk", "0"s},
  915. {"value", INT64_C(0)},
  916. };
  917. object_schema = realm->schema().find("string pk");
  918. Object::create(ctx, realm, *object_schema, values);
  919. realm->commit_transaction();
  920. // Verify results
  921. auto table = get_table(realm, "string pk");
  922. REQUIRE(table->size() == 11);
  923. REQUIRE(table->get_primary_key_column() == table->get_column_key("pk"));
  924. for (auto& obj : *table) {
  925. REQUIRE(util::to_string(obj.get<int64_t>("value")).c_str() == obj.get<StringData>("pk"));
  926. }
  927. }
  928. SECTION("create and modify int primary key inside migration") {
  929. SECTION("with index") {
  930. realm->begin_transaction();
  931. auto table = get_table(realm, "int pk");
  932. table->add_search_index(table->get_column_key("pk"));
  933. realm->commit_transaction();
  934. }
  935. SECTION("no index") {
  936. }
  937. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  938. CppContext ctx(new_realm);
  939. for (int64_t i = 0; i < 10; ++i) {
  940. auto obj = Object::create(ctx, new_realm, *new_realm->schema().find("int pk"),
  941. util::Any(AnyDict{
  942. {"pk", INT64_C(0)},
  943. {"value", i}
  944. }));
  945. obj.set_property_value(ctx, "pk", util::Any(i));
  946. }
  947. });
  948. auto table = get_table(realm, "int pk");
  949. REQUIRE(table->size() == 10);
  950. REQUIRE(table->get_primary_key_column() == table->get_column_key("pk"));
  951. for (int i = 0; i < 10; ++i) {
  952. auto obj = table->get_object(i);
  953. REQUIRE(obj.get<int64_t>("pk") == i);
  954. REQUIRE(obj.get<int64_t>("value") == i);
  955. }
  956. }
  957. SECTION("create and modify string primary key inside migration") {
  958. SECTION("with index") {
  959. realm->begin_transaction();
  960. auto table = get_table(realm, "string pk");
  961. table->add_search_index(table->get_column_key("pk"));
  962. realm->commit_transaction();
  963. }
  964. SECTION("no index") {
  965. }
  966. realm->update_schema(schema, 2, [](auto, auto new_realm, Schema&) {
  967. CppContext ctx(new_realm);
  968. for (int64_t i = 0; i < 10; ++i) {
  969. auto obj = Object::create(ctx, new_realm, *new_realm->schema().find("string pk"),
  970. util::Any(AnyDict{
  971. {"pk", ""s},
  972. {"value", i}
  973. }));
  974. obj.set_property_value(ctx, "pk", util::Any(util::to_string(i)));
  975. }
  976. });
  977. auto table = get_table(realm, "string pk");
  978. REQUIRE(table->size() == 10);
  979. REQUIRE(table->get_primary_key_column() == table->get_column_key("pk"));
  980. for (auto& obj : *table)
  981. REQUIRE(obj.get<StringData>("pk") == util::to_string(obj.get<int64_t>("value")).c_str());
  982. }
  983. SECTION("create object after adding primary key") {
  984. schema = set_primary_key(schema, "all types", "");
  985. realm->update_schema(schema, 2);
  986. schema = set_primary_key(schema, "all types", "pk");
  987. REQUIRE_NOTHROW(realm->update_schema(schema, 3, [&](auto, auto new_realm, Schema&) {
  988. CppContext ctx(new_realm);
  989. any_cast<AnyDict&>(values)["pk"] = INT64_C(2);
  990. Object::create(ctx, realm, "all types", values);
  991. }));
  992. }
  993. }
  994. SECTION("property renaming") {
  995. InMemoryTestFile config;
  996. config.schema_mode = SchemaMode::Automatic;
  997. auto realm = Realm::get_shared_realm(config);
  998. struct Rename {
  999. StringData object_type;
  1000. StringData old_name;
  1001. StringData new_name;
  1002. };
  1003. auto apply_renames = [&](std::initializer_list<Rename> renames) -> Realm::MigrationFunction {
  1004. return [=](SharedRealm, SharedRealm realm, Schema& schema) {
  1005. for (auto rename : renames) {
  1006. ObjectStore::rename_property(realm->read_group(), schema,
  1007. rename.object_type, rename.old_name, rename.new_name);
  1008. }
  1009. };
  1010. };
  1011. #define FAILED_RENAME(old_schema, new_schema, error, ...) do { \
  1012. realm->update_schema(old_schema, 1); \
  1013. REQUIRE_THROWS_WITH(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__})), error); \
  1014. } while (false)
  1015. Schema schema = {
  1016. {"object", {
  1017. {"value", PropertyType::Int},
  1018. }},
  1019. };
  1020. SECTION("table does not exist in old schema") {
  1021. auto schema2 = add_table(schema, {"object 2", {
  1022. {"value 2", PropertyType::Int},
  1023. }});
  1024. FAILED_RENAME(schema, schema2,
  1025. "Cannot rename property 'object 2.value' because it does not exist.",
  1026. {"object 2", "value", "value 2"});
  1027. }
  1028. SECTION("table does not exist in new schema") {
  1029. FAILED_RENAME(schema, {},
  1030. "Cannot rename properties for type 'object' because it has been removed from the Realm.",
  1031. {"object", "value", "value 2"});
  1032. }
  1033. SECTION("property does not exist in old schema") {
  1034. auto schema2 = add_property(schema, "object", {"new", PropertyType::Int});
  1035. FAILED_RENAME(schema, schema2,
  1036. "Cannot rename property 'object.nonexistent' because it does not exist.",
  1037. {"object", "nonexistent", "new"});
  1038. }
  1039. auto rename_value = [](Schema schema) {
  1040. schema.find("object")->property_for_name("value")->name = "new";
  1041. return schema;
  1042. };
  1043. SECTION("property does not exist in new schema") {
  1044. FAILED_RENAME(schema, rename_value(schema),
  1045. "Renamed property 'object.nonexistent' does not exist.",
  1046. {"object", "value", "nonexistent"});
  1047. }
  1048. SECTION("source propety still exists in the new schema") {
  1049. auto schema2 = add_property(schema, "object",
  1050. {"new", PropertyType::Int});
  1051. FAILED_RENAME(schema, schema2,
  1052. "Cannot rename property 'object.value' to 'new' because the source property still exists.",
  1053. {"object", "value", "new"});
  1054. }
  1055. SECTION("different type") {
  1056. auto schema2 = rename_value(set_type(schema, "object", "value", PropertyType::Date));
  1057. FAILED_RENAME(schema, schema2,
  1058. "Cannot rename property 'object.value' to 'new' because it would change from type 'int' to 'date'.",
  1059. {"object", "value", "new"});
  1060. }
  1061. SECTION("different link targets") {
  1062. Schema schema = {
  1063. {"target", {
  1064. {"value", PropertyType::Int},
  1065. }},
  1066. {"origin", {
  1067. {"link", PropertyType::Object|PropertyType::Nullable, "target"},
  1068. }},
  1069. };
  1070. auto schema2 = set_target(schema, "origin", "link", "origin");
  1071. schema2.find("origin")->property_for_name("link")->name = "new";
  1072. FAILED_RENAME(schema, schema2,
  1073. "Cannot rename property 'origin.link' to 'new' because it would change from type '<target>' to '<origin>'.",
  1074. {"origin", "link", "new"});
  1075. }
  1076. SECTION("different linklist targets") {
  1077. Schema schema = {
  1078. {"target", {
  1079. {"value", PropertyType::Int},
  1080. }},
  1081. {"origin", {
  1082. {"link", PropertyType::Array|PropertyType::Object, "target"},
  1083. }},
  1084. };
  1085. auto schema2 = set_target(schema, "origin", "link", "origin");
  1086. schema2.find("origin")->property_for_name("link")->name = "new";
  1087. FAILED_RENAME(schema, schema2,
  1088. "Cannot rename property 'origin.link' to 'new' because it would change from type 'array<target>' to 'array<origin>'.",
  1089. {"origin", "link", "new"});
  1090. }
  1091. SECTION("make required") {
  1092. schema = set_optional(schema, "object", "value", true);
  1093. auto schema2 = rename_value(set_optional(schema, "object", "value", false));
  1094. FAILED_RENAME(schema, schema2,
  1095. "Cannot rename property 'object.value' to 'new' because it would change from optional to required.",
  1096. {"object", "value", "new"});
  1097. }
  1098. auto init = [&](Schema const& old_schema) {
  1099. realm->update_schema(old_schema, 1);
  1100. realm->begin_transaction();
  1101. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1102. table->create_object().set_all(10);
  1103. realm->commit_transaction();
  1104. };
  1105. #define SUCCESSFUL_RENAME(old_schema, new_schema, ...) do { \
  1106. init(old_schema); \
  1107. REQUIRE_NOTHROW(realm->update_schema(new_schema, 2, apply_renames({__VA_ARGS__}))); \
  1108. REQUIRE(realm->schema() == new_schema); \
  1109. VERIFY_SCHEMA(*realm, false); \
  1110. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object"); \
  1111. auto key = table->get_column_keys()[0]; \
  1112. if (table->get_column_attr(key).test(col_attr_Nullable)) \
  1113. REQUIRE(table->begin()->get<util::Optional<int64_t>>(key) == 10); \
  1114. else \
  1115. REQUIRE(table->begin()->get<int64_t>(key) == 10); \
  1116. } while (false)
  1117. SECTION("basic valid rename") {
  1118. auto schema2 = rename_value(schema);
  1119. SUCCESSFUL_RENAME(schema, schema2,
  1120. {"object", "value", "new"});
  1121. }
  1122. SECTION("chained rename") {
  1123. auto schema2 = rename_value(schema);
  1124. SUCCESSFUL_RENAME(schema, schema2,
  1125. {"object", "value", "a"},
  1126. {"object", "a", "b"},
  1127. {"object", "b", "new"});
  1128. }
  1129. SECTION("old is pk, new is not") {
  1130. auto schema2 = rename_value(schema);
  1131. schema = set_primary_key(schema, "object", "value");
  1132. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  1133. }
  1134. SECTION("new is pk, old is not") {
  1135. auto schema2 = set_primary_key(rename_value(schema), "object", "new");
  1136. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  1137. }
  1138. SECTION("both are pk") {
  1139. schema = set_primary_key(schema, "object", "value");
  1140. auto schema2 = set_primary_key(rename_value(schema), "object", "new");
  1141. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  1142. }
  1143. SECTION("make optional") {
  1144. auto schema2 = rename_value(set_optional(schema, "object", "value", true));
  1145. SUCCESSFUL_RENAME(schema, schema2,
  1146. {"object", "value", "new"});
  1147. }
  1148. SECTION("add index") {
  1149. auto schema2 = rename_value(set_indexed(schema, "object", "value", true));
  1150. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  1151. }
  1152. SECTION("remove index") {
  1153. auto schema2 = rename_value(schema);
  1154. schema = set_indexed(schema, "object", "value", true);
  1155. SUCCESSFUL_RENAME(schema, schema2, {"object", "value", "new"});
  1156. }
  1157. SECTION("create object inside migration after renaming pk") {
  1158. schema = set_primary_key(schema, "object", "value");
  1159. auto new_schema = set_primary_key(rename_value(schema), "object", "new");
  1160. init(schema);
  1161. REQUIRE_NOTHROW(realm->update_schema(new_schema, 2, [](auto, auto realm, Schema& schema) {
  1162. ObjectStore::rename_property(realm->read_group(), schema,
  1163. "object", "value", "new");
  1164. CppContext ctx(realm);
  1165. util::Any values = AnyDict{{"new", INT64_C(11)}};
  1166. Object::create(ctx, realm, "object", values);
  1167. }));
  1168. REQUIRE(realm->schema() == new_schema);
  1169. VERIFY_SCHEMA(*realm, false);
  1170. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1171. auto key = table->get_column_keys()[0];
  1172. auto it = table->begin();
  1173. REQUIRE(it->get<int64_t>(key) == 10);
  1174. REQUIRE((++it)->get<int64_t>(key) == 11);
  1175. }
  1176. }
  1177. }
  1178. TEST_CASE("migration: Immutable") {
  1179. TestFile config;
  1180. auto realm_with_schema = [&](Schema schema) {
  1181. {
  1182. auto realm = Realm::get_shared_realm(config);
  1183. realm->update_schema(std::move(schema));
  1184. }
  1185. config.schema_mode = SchemaMode::Immutable;
  1186. return Realm::get_shared_realm(config);
  1187. };
  1188. SECTION("allowed schema mismatches") {
  1189. SECTION("index") {
  1190. auto realm = realm_with_schema({
  1191. {"object", {
  1192. {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1193. {"unindexed", PropertyType::Int},
  1194. }},
  1195. });
  1196. Schema schema = {
  1197. {"object", {
  1198. {"indexed", PropertyType::Int},
  1199. {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1200. }},
  1201. };
  1202. REQUIRE_NOTHROW(realm->update_schema(schema));
  1203. REQUIRE(realm->schema() == schema);
  1204. }
  1205. SECTION("extra tables") {
  1206. auto realm = realm_with_schema({
  1207. {"object", {
  1208. {"value", PropertyType::Int},
  1209. }},
  1210. {"object 2", {
  1211. {"value", PropertyType::Int},
  1212. }},
  1213. });
  1214. Schema schema = {
  1215. {"object", {
  1216. {"value", PropertyType::Int},
  1217. }},
  1218. };
  1219. REQUIRE_NOTHROW(realm->update_schema(schema));
  1220. }
  1221. SECTION("missing tables") {
  1222. auto realm = realm_with_schema({
  1223. {"object", {
  1224. {"value", PropertyType::Int},
  1225. }},
  1226. });
  1227. Schema schema = {
  1228. {"object", {
  1229. {"value", PropertyType::Int},
  1230. }},
  1231. {"second object", {
  1232. {"value", PropertyType::Int},
  1233. }},
  1234. };
  1235. REQUIRE_NOTHROW(realm->update_schema(schema));
  1236. REQUIRE(realm->schema() == schema);
  1237. auto object_schema = realm->schema().find("object");
  1238. REQUIRE(object_schema->persisted_properties.size() == 1);
  1239. REQUIRE(object_schema->persisted_properties[0].column_key);
  1240. object_schema = realm->schema().find("second object");
  1241. REQUIRE(object_schema->persisted_properties.size() == 1);
  1242. REQUIRE(!object_schema->persisted_properties[0].column_key);
  1243. }
  1244. SECTION("extra columns in table") {
  1245. auto realm = realm_with_schema({
  1246. {"object", {
  1247. {"value", PropertyType::Int},
  1248. {"value 2", PropertyType::Int},
  1249. }},
  1250. });
  1251. Schema schema = {
  1252. {"object", {
  1253. {"value", PropertyType::Int},
  1254. }},
  1255. };
  1256. REQUIRE_NOTHROW(realm->update_schema(schema));
  1257. }
  1258. }
  1259. SECTION("disallowed mismatches") {
  1260. SECTION("missing columns in table") {
  1261. auto realm = realm_with_schema({
  1262. {"object", {
  1263. {"value", PropertyType::Int},
  1264. }},
  1265. });
  1266. Schema schema = {
  1267. {"object", {
  1268. {"value", PropertyType::Int},
  1269. {"value 2", PropertyType::Int},
  1270. }},
  1271. };
  1272. REQUIRE_THROWS(realm->update_schema(schema));
  1273. }
  1274. SECTION("bump schema version") {
  1275. Schema schema = {
  1276. {"object", {
  1277. {"value", PropertyType::Int},
  1278. }},
  1279. };
  1280. auto realm = realm_with_schema(schema);
  1281. REQUIRE_THROWS(realm->update_schema(schema, 1));
  1282. }
  1283. }
  1284. }
  1285. TEST_CASE("migration: ReadOnly") {
  1286. TestFile config;
  1287. auto realm_with_schema = [&](Schema schema) {
  1288. {
  1289. auto realm = Realm::get_shared_realm(config);
  1290. realm->update_schema(std::move(schema));
  1291. }
  1292. config.schema_mode = SchemaMode::ReadOnlyAlternative;
  1293. return Realm::get_shared_realm(config);
  1294. };
  1295. SECTION("allowed schema mismatches") {
  1296. SECTION("index") {
  1297. auto realm = realm_with_schema({
  1298. {"object", {
  1299. {"indexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1300. {"unindexed", PropertyType::Int},
  1301. }},
  1302. });
  1303. Schema schema = {
  1304. {"object", {
  1305. {"indexed", PropertyType::Int},
  1306. {"unindexed", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1307. }},
  1308. };
  1309. REQUIRE_NOTHROW(realm->update_schema(schema));
  1310. REQUIRE(realm->schema() == schema);
  1311. }
  1312. SECTION("extra tables") {
  1313. auto realm = realm_with_schema({
  1314. {"object", {
  1315. {"value", PropertyType::Int},
  1316. }},
  1317. {"object 2", {
  1318. {"value", PropertyType::Int},
  1319. }},
  1320. });
  1321. Schema schema = {
  1322. {"object", {
  1323. {"value", PropertyType::Int},
  1324. }},
  1325. };
  1326. REQUIRE_NOTHROW(realm->update_schema(schema));
  1327. }
  1328. SECTION("extra columns in table") {
  1329. auto realm = realm_with_schema({
  1330. {"object", {
  1331. {"value", PropertyType::Int},
  1332. {"value 2", PropertyType::Int},
  1333. }},
  1334. });
  1335. Schema schema = {
  1336. {"object", {
  1337. {"value", PropertyType::Int},
  1338. }},
  1339. };
  1340. REQUIRE_NOTHROW(realm->update_schema(schema));
  1341. }
  1342. SECTION("missing tables") {
  1343. auto realm = realm_with_schema({
  1344. {"object", {
  1345. {"value", PropertyType::Int},
  1346. }},
  1347. });
  1348. Schema schema = {
  1349. {"object", {
  1350. {"value", PropertyType::Int},
  1351. }},
  1352. {"second object", {
  1353. {"value", PropertyType::Int},
  1354. }},
  1355. };
  1356. REQUIRE_NOTHROW(realm->update_schema(schema));
  1357. }
  1358. SECTION("bump schema version") {
  1359. Schema schema = {
  1360. {"object", {
  1361. {"value", PropertyType::Int},
  1362. }},
  1363. };
  1364. auto realm = realm_with_schema(schema);
  1365. REQUIRE_NOTHROW(realm->update_schema(schema, 1));
  1366. }
  1367. }
  1368. SECTION("disallowed mismatches") {
  1369. SECTION("missing columns in table") {
  1370. auto realm = realm_with_schema({
  1371. {"object", {
  1372. {"value", PropertyType::Int},
  1373. }},
  1374. });
  1375. Schema schema = {
  1376. {"object", {
  1377. {"value", PropertyType::Int},
  1378. {"value 2", PropertyType::Int},
  1379. }},
  1380. };
  1381. REQUIRE_THROWS(realm->update_schema(schema));
  1382. }
  1383. }
  1384. }
  1385. TEST_CASE("migration: ResetFile") {
  1386. TestFile config;
  1387. config.schema_mode = SchemaMode::ResetFile;
  1388. Schema schema = {
  1389. {"object", {
  1390. {"value", PropertyType::Int},
  1391. }},
  1392. {"object 2", {
  1393. {"value", PropertyType::Int},
  1394. }},
  1395. };
  1396. // To verify that the file has actually be deleted and recreated, on
  1397. // non-Windows we need to hold an open file handle to the old file to force
  1398. // using a new inode, but on Windows we *can't*
  1399. #ifdef _WIN32
  1400. auto get_fileid = [&] {
  1401. // this is wrong for non-ascii but it's what core does
  1402. std::wstring ws(config.path.begin(), config.path.end());
  1403. HANDLE handle = CreateFile2(ws.c_str(), GENERIC_READ,
  1404. FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING,
  1405. nullptr);
  1406. REQUIRE(handle != INVALID_HANDLE_VALUE);
  1407. auto close = util::make_scope_exit([=]() noexcept { CloseHandle(handle); });
  1408. BY_HANDLE_FILE_INFORMATION info{};
  1409. REQUIRE(GetFileInformationByHandle(handle, &info));
  1410. return (DWORDLONG)info.nFileIndexHigh + (DWORDLONG)info.nFileIndexLow;
  1411. };
  1412. #else
  1413. auto get_fileid = [&] {
  1414. util::File::UniqueID id;
  1415. util::File::get_unique_id(config.path, id);
  1416. return id.inode;
  1417. };
  1418. File holder(config.path, File::mode_Write);
  1419. #endif
  1420. {
  1421. auto realm = Realm::get_shared_realm(config);
  1422. auto ino = get_fileid();
  1423. realm->update_schema(schema);
  1424. REQUIRE(ino == get_fileid());
  1425. realm->begin_transaction();
  1426. ObjectStore::table_for_object_type(realm->read_group(), "object")->create_object();
  1427. realm->commit_transaction();
  1428. }
  1429. auto realm = Realm::get_shared_realm(config);
  1430. auto ino = get_fileid();
  1431. SECTION("file is reset when schema version increases") {
  1432. realm->update_schema(schema, 1);
  1433. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0);
  1434. REQUIRE(ino != get_fileid());
  1435. }
  1436. SECTION("file is reset when an existing table is modified") {
  1437. realm->update_schema(add_property(schema, "object",
  1438. {"value 2", PropertyType::Int}));
  1439. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 0);
  1440. REQUIRE(ino != get_fileid());
  1441. }
  1442. SECTION("file is not reset when adding a new table") {
  1443. realm->update_schema(add_table(schema, {"object 3", {
  1444. {"value", PropertyType::Int},
  1445. }}));
  1446. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1447. REQUIRE(realm->schema().size() == 3);
  1448. REQUIRE(ino == get_fileid());
  1449. }
  1450. SECTION("file is not reset when removing a table") {
  1451. realm->update_schema(remove_table(schema, "object 2"));
  1452. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1453. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2"));
  1454. REQUIRE(realm->schema().size() == 1);
  1455. REQUIRE(ino == get_fileid());
  1456. }
  1457. SECTION("file is not reset when adding an index") {
  1458. realm->update_schema(set_indexed(schema, "object", "value", true));
  1459. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1460. REQUIRE(ino == get_fileid());
  1461. }
  1462. SECTION("file is not reset when removing an index") {
  1463. realm->update_schema(set_indexed(schema, "object", "value", true));
  1464. realm->update_schema(schema);
  1465. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->size() == 1);
  1466. REQUIRE(ino == get_fileid());
  1467. }
  1468. }
  1469. TEST_CASE("migration: Additive") {
  1470. Schema schema = {
  1471. {"object", {
  1472. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1473. {"value 2", PropertyType::Int|PropertyType::Nullable},
  1474. }},
  1475. };
  1476. TestFile config;
  1477. config.schema_mode = SchemaMode::Additive;
  1478. config.schema = schema;
  1479. auto realm = Realm::get_shared_realm(config);
  1480. realm->update_schema(schema);
  1481. SECTION("can add new properties to existing tables") {
  1482. REQUIRE_NOTHROW(realm->update_schema(add_property(schema, "object",
  1483. {"value 3", PropertyType::Int})));
  1484. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 3);
  1485. }
  1486. SECTION("can add new tables") {
  1487. REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
  1488. {"value", PropertyType::Int},
  1489. }})));
  1490. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object"));
  1491. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object 2"));
  1492. }
  1493. SECTION("indexes are updated when schema version is bumped") {
  1494. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1495. auto col_keys = table->get_column_keys();
  1496. REQUIRE(table->has_search_index(col_keys[0]));
  1497. REQUIRE(!table->has_search_index(col_keys[1]));
  1498. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false), 1));
  1499. REQUIRE(!table->has_search_index(col_keys[0]));
  1500. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true), 2));
  1501. REQUIRE(table->has_search_index(col_keys[1]));
  1502. }
  1503. SECTION("indexes are not updated when schema version is not bumped") {
  1504. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1505. auto col_keys = table->get_column_keys();
  1506. REQUIRE(table->has_search_index(col_keys[0]));
  1507. REQUIRE(!table->has_search_index(col_keys[1]));
  1508. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value", false)));
  1509. REQUIRE(table->has_search_index(col_keys[0]));
  1510. REQUIRE_NOTHROW(realm->update_schema(set_indexed(schema, "object", "value 2", true)));
  1511. REQUIRE(!table->has_search_index(col_keys[1]));
  1512. }
  1513. SECTION("can remove properties from existing tables, but column is not removed") {
  1514. auto table = ObjectStore::table_for_object_type(realm->read_group(), "object");
  1515. REQUIRE_NOTHROW(realm->update_schema(remove_property(schema, "object", "value")));
  1516. REQUIRE(ObjectStore::table_for_object_type(realm->read_group(), "object")->get_column_count() == 2);
  1517. auto const& properties = realm->schema().find("object")->persisted_properties;
  1518. REQUIRE(properties.size() == 1);
  1519. auto col_keys = table->get_column_keys();
  1520. REQUIRE(col_keys.size() == 2);
  1521. REQUIRE(properties[0].column_key == col_keys[1]);
  1522. }
  1523. SECTION("cannot change existing property types") {
  1524. REQUIRE_THROWS(realm->update_schema(set_type(schema, "object", "value", PropertyType::Float)));
  1525. }
  1526. SECTION("cannot change existing property nullability") {
  1527. REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value", true)));
  1528. REQUIRE_THROWS(realm->update_schema(set_optional(schema, "object", "value 2", false)));
  1529. }
  1530. SECTION("cannot change existing link targets") {
  1531. REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
  1532. {"link", PropertyType::Object|PropertyType::Nullable, "object"},
  1533. }})));
  1534. REQUIRE_THROWS(realm->update_schema(set_target(realm->schema(), "object 2", "link", "object 2")));
  1535. }
  1536. SECTION("cannot change primary keys") {
  1537. REQUIRE_THROWS(realm->update_schema(set_primary_key(schema, "object", "value")));
  1538. REQUIRE_NOTHROW(realm->update_schema(add_table(schema, {"object 2", {
  1539. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  1540. }})));
  1541. REQUIRE_THROWS(realm->update_schema(set_primary_key(realm->schema(), "object 2", "")));
  1542. }
  1543. SECTION("schema version is allowed to go down") {
  1544. REQUIRE_NOTHROW(realm->update_schema(schema, 1));
  1545. REQUIRE(realm->schema_version() == 1);
  1546. REQUIRE_NOTHROW(realm->update_schema(schema, 0));
  1547. REQUIRE(realm->schema_version() == 1);
  1548. }
  1549. SECTION("migration function is not used") {
  1550. REQUIRE_NOTHROW(realm->update_schema(schema, 1,
  1551. [&](SharedRealm, SharedRealm, Schema&) { REQUIRE(false); }));
  1552. }
  1553. SECTION("add new columns from different SG") {
  1554. auto realm2 = Realm::get_shared_realm(config);
  1555. auto& group = realm2->read_group();
  1556. realm2->begin_transaction();
  1557. auto table = ObjectStore::table_for_object_type(group, "object");
  1558. auto col_keys = table->get_column_keys();
  1559. table->add_column(type_Int, "new column");
  1560. realm2->commit_transaction();
  1561. REQUIRE_NOTHROW(realm->refresh());
  1562. REQUIRE(realm->schema() == schema);
  1563. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1564. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1565. }
  1566. SECTION("opening new Realms uses the correct schema after an external change") {
  1567. auto realm2 = Realm::get_shared_realm(config);
  1568. auto& group = realm2->read_group();
  1569. realm2->begin_transaction();
  1570. auto table = ObjectStore::table_for_object_type(group, "object");
  1571. auto col_keys = table->get_column_keys();
  1572. table->add_column(type_Double, "newcol");
  1573. realm2->commit_transaction();
  1574. REQUIRE_NOTHROW(realm->refresh());
  1575. REQUIRE(realm->schema() == schema);
  1576. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1577. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1578. // Gets the schema from the RealmCoordinator
  1579. auto realm3 = Realm::get_shared_realm(config);
  1580. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1581. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1582. // Close and re-open the file entirely so that the coordinator is recreated
  1583. realm.reset();
  1584. realm2.reset();
  1585. realm3.reset();
  1586. realm = Realm::get_shared_realm(config);
  1587. REQUIRE(realm->schema() == schema);
  1588. REQUIRE(realm->schema().find("object")->persisted_properties[0].column_key == col_keys[0]);
  1589. REQUIRE(realm->schema().find("object")->persisted_properties[1].column_key == col_keys[1]);
  1590. }
  1591. SECTION("can have different subsets of columns in different Realm instances") {
  1592. auto config2 = config;
  1593. config2.schema = add_property(schema, "object",
  1594. {"value 3", PropertyType::Int});
  1595. auto config3 = config;
  1596. config3.schema = remove_property(schema, "object", "value 2");
  1597. auto config4 = config;
  1598. config4.schema = util::none;
  1599. auto realm2 = Realm::get_shared_realm(config2);
  1600. auto realm3 = Realm::get_shared_realm(config3);
  1601. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
  1602. REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3);
  1603. REQUIRE(realm3->schema().find("object")->persisted_properties.size() == 1);
  1604. realm->refresh();
  1605. realm2->refresh();
  1606. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
  1607. REQUIRE(realm2->schema().find("object")->persisted_properties.size() == 3);
  1608. // No schema specified; should see all of them
  1609. auto realm4 = Realm::get_shared_realm(config4);
  1610. REQUIRE(realm4->schema().find("object")->persisted_properties.size() == 3);
  1611. }
  1612. SECTION("updating a schema to include already-present column") {
  1613. auto config2 = config;
  1614. config2.schema = add_property(schema, "object",
  1615. {"value 3", PropertyType::Int});
  1616. auto realm2 = Realm::get_shared_realm(config2);
  1617. auto& properties2 = realm2->schema().find("object")->persisted_properties;
  1618. REQUIRE_NOTHROW(realm->update_schema(*config2.schema));
  1619. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 3);
  1620. auto& properties = realm->schema().find("object")->persisted_properties;
  1621. REQUIRE(properties[0].column_key == properties2[0].column_key);
  1622. REQUIRE(properties[1].column_key == properties2[1].column_key);
  1623. REQUIRE(properties[2].column_key == properties2[2].column_key);
  1624. }
  1625. SECTION("increasing schema version without modifying schema properly leaves the schema untouched") {
  1626. TestFile config1;
  1627. config1.schema = schema;
  1628. config1.schema_mode = SchemaMode::Additive;
  1629. config1.schema_version = 0;
  1630. auto realm1 = Realm::get_shared_realm(config1);
  1631. REQUIRE(realm1->schema().size() == 1);
  1632. Schema schema1 = realm1->schema();
  1633. realm1->close();
  1634. auto config2 = config1;
  1635. config2.schema_version = 1;
  1636. auto realm2 = Realm::get_shared_realm(config2);
  1637. REQUIRE(realm2->schema() == schema1);
  1638. }
  1639. SECTION("invalid schema update leaves the schema untouched") {
  1640. auto config2 = config;
  1641. config2.schema = add_property(schema, "object", {"value 3", PropertyType::Int});
  1642. auto realm2 = Realm::get_shared_realm(config2);
  1643. REQUIRE_THROWS(realm->update_schema(add_property(schema, "object", {"value 3", PropertyType::Float})));
  1644. REQUIRE(realm->schema().find("object")->persisted_properties.size() == 2);
  1645. }
  1646. SECTION("update_schema() does not begin a write transaction when extra columns are present") {
  1647. realm->begin_transaction();
  1648. auto realm2 = Realm::get_shared_realm(config);
  1649. // will deadlock if it tries to start a write transaction
  1650. realm2->update_schema(remove_property(schema, "object", "value"));
  1651. }
  1652. SECTION("update_schema() does not begin a write transaction when indexes are changed without bumping schema version") {
  1653. realm->begin_transaction();
  1654. auto realm2 = Realm::get_shared_realm(config);
  1655. // will deadlock if it tries to start a write transaction
  1656. realm->update_schema(set_indexed(schema, "object", "value 2", true));
  1657. }
  1658. SECTION("update_schema() does not begin a write transaction for invalid schema changes") {
  1659. realm->begin_transaction();
  1660. auto realm2 = Realm::get_shared_realm(config);
  1661. auto new_schema = add_property(remove_property(schema, "object", "value"),
  1662. "object", {"value", PropertyType::Float});
  1663. // will deadlock if it tries to start a write transaction
  1664. REQUIRE_THROWS(realm2->update_schema(new_schema));
  1665. }
  1666. }
  1667. TEST_CASE("migration: Manual") {
  1668. TestFile config;
  1669. config.schema_mode = SchemaMode::Manual;
  1670. auto realm = Realm::get_shared_realm(config);
  1671. Schema schema = {
  1672. {"object", {
  1673. {"pk", PropertyType::Int, Property::IsPrimary{true}},
  1674. {"value", PropertyType::Int, Property::IsPrimary{false}, Property::IsIndexed{true}},
  1675. {"optional", PropertyType::Int|PropertyType::Nullable},
  1676. }},
  1677. {"link origin", {
  1678. {"not a pk", PropertyType::Int},
  1679. {"object", PropertyType::Object|PropertyType::Nullable, "object"},
  1680. {"array", PropertyType::Array|PropertyType::Object, "object"},
  1681. }}
  1682. };
  1683. realm->update_schema(schema);
  1684. auto col_keys = realm->read_group().get_table("class_object")->get_column_keys();
  1685. #define REQUIRE_MIGRATION(schema, migration) do { \
  1686. Schema new_schema = (schema); \
  1687. REQUIRE_THROWS(realm->update_schema(new_schema)); \
  1688. REQUIRE(realm->schema_version() == 0); \
  1689. REQUIRE_THROWS(realm->update_schema(new_schema, 1, [](SharedRealm, SharedRealm, Schema&){})); \
  1690. REQUIRE(realm->schema_version() == 0); \
  1691. REQUIRE_NOTHROW(realm->update_schema(new_schema, 1, migration)); \
  1692. REQUIRE(realm->schema_version() == 1); \
  1693. } while (false)
  1694. SECTION("add new table") {
  1695. REQUIRE_MIGRATION(add_table(schema, {"new table", {
  1696. {"value", PropertyType::Int},
  1697. }}), [](SharedRealm, SharedRealm realm, Schema&) {
  1698. realm->read_group().add_table("class_new table")->add_column(type_Int, "value");
  1699. });
  1700. }
  1701. SECTION("add property to table") {
  1702. REQUIRE_MIGRATION(add_property(schema, "object", {"new", PropertyType::Int}),
  1703. [&](SharedRealm, SharedRealm realm, Schema&) {
  1704. get_table(realm, "object")->add_column(type_Int, "new");
  1705. });
  1706. }
  1707. SECTION("remove property from table") {
  1708. REQUIRE_MIGRATION(remove_property(schema, "object", "value"),
  1709. [&](SharedRealm, SharedRealm realm, Schema&) {
  1710. get_table(realm, "object")->remove_column(col_keys[1]);
  1711. });
  1712. }
  1713. SECTION("add primary key to table") {
  1714. REQUIRE_MIGRATION(set_primary_key(schema, "link origin", "not a pk"),
  1715. [&](SharedRealm, SharedRealm realm, Schema&) {
  1716. ObjectStore::set_primary_key_for_object(realm->read_group(), "link origin", "not a pk");
  1717. auto table = get_table(realm, "link origin");
  1718. table->add_search_index(table->get_column_key("not a pk"));
  1719. });
  1720. }
  1721. SECTION("remove primary key from table") {
  1722. REQUIRE_MIGRATION(set_primary_key(schema, "object", ""),
  1723. [&](SharedRealm, SharedRealm realm, Schema&) {
  1724. ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "");
  1725. get_table(realm, "object")->remove_search_index(col_keys[0]);
  1726. });
  1727. }
  1728. SECTION("change primary key") {
  1729. REQUIRE_MIGRATION(set_primary_key(schema, "object", "value"),
  1730. [&](SharedRealm, SharedRealm realm, Schema&) {
  1731. ObjectStore::set_primary_key_for_object(realm->read_group(), "object", "value");
  1732. auto table = get_table(realm, "object");
  1733. table->remove_search_index(col_keys[0]);
  1734. table->add_search_index(col_keys[1]);
  1735. });
  1736. }
  1737. SECTION("change property type") {
  1738. REQUIRE_MIGRATION(set_type(schema, "object", "value", PropertyType::Date),
  1739. [&](SharedRealm, SharedRealm realm, Schema&) {
  1740. auto table = get_table(realm, "object");
  1741. table->remove_column(col_keys[1]);
  1742. auto col = table->add_column(type_Timestamp, "value");
  1743. table->add_search_index(col);
  1744. });
  1745. }
  1746. SECTION("change link target") {
  1747. REQUIRE_MIGRATION(set_target(schema, "link origin", "object", "link origin"),
  1748. [&](SharedRealm, SharedRealm realm, Schema&) {
  1749. auto table = get_table(realm, "link origin");
  1750. table->remove_column(table->get_column_keys()[1]);
  1751. table->add_column_link(type_Link, "object", *table);
  1752. });
  1753. }
  1754. SECTION("change linklist target") {
  1755. REQUIRE_MIGRATION(set_target(schema, "link origin", "array", "link origin"),
  1756. [&](SharedRealm, SharedRealm realm, Schema&) {
  1757. auto table = get_table(realm, "link origin");
  1758. table->remove_column(table->get_column_keys()[2]);
  1759. table->add_column_link(type_LinkList, "array", *table);
  1760. });
  1761. }
  1762. SECTION("make property optional") {
  1763. REQUIRE_MIGRATION(set_optional(schema, "object", "value", true),
  1764. [&](SharedRealm, SharedRealm realm, Schema&) {
  1765. auto table = get_table(realm, "object");
  1766. table->remove_column(col_keys[1]);
  1767. auto col = table->add_column(type_Int, "value", true);
  1768. table->add_search_index(col);
  1769. });
  1770. }
  1771. SECTION("make property required") {
  1772. REQUIRE_MIGRATION(set_optional(schema, "object", "optional", false),
  1773. [&](SharedRealm, SharedRealm realm, Schema&) {
  1774. auto table = get_table(realm, "object");
  1775. table->remove_column(col_keys[2]);
  1776. table->add_column(type_Int, "optional", false);
  1777. });
  1778. }
  1779. SECTION("add index") {
  1780. REQUIRE_MIGRATION(set_indexed(schema, "object", "optional", true),
  1781. [&](SharedRealm, SharedRealm realm, Schema&) {
  1782. get_table(realm, "object")->add_search_index(col_keys[2]);
  1783. });
  1784. }
  1785. SECTION("remove index") {
  1786. REQUIRE_MIGRATION(set_indexed(schema, "object", "value", false),
  1787. [&](SharedRealm, SharedRealm realm, Schema&) {
  1788. get_table(realm, "object")->remove_search_index(col_keys[1]);
  1789. });
  1790. }
  1791. SECTION("reorder properties") {
  1792. auto schema2 = schema;
  1793. auto& properties = schema2.find("object")->persisted_properties;
  1794. std::swap(properties[0], properties[1]);
  1795. REQUIRE_NOTHROW(realm->update_schema(schema2));
  1796. }
  1797. SECTION("cannot lower schema version") {
  1798. REQUIRE_NOTHROW(realm->update_schema(schema, 1, [](SharedRealm, SharedRealm, Schema&){}));
  1799. REQUIRE(realm->schema_version() == 1);
  1800. REQUIRE_THROWS(realm->update_schema(schema, 0, [](SharedRealm, SharedRealm, Schema&){}));
  1801. REQUIRE(realm->schema_version() == 1);
  1802. }
  1803. SECTION("update_schema() does not begin a write transaction when schema version is unchanged") {
  1804. realm->begin_transaction();
  1805. auto realm2 = Realm::get_shared_realm(config);
  1806. // will deadlock if it tries to start a write transaction
  1807. REQUIRE_NOTHROW(realm2->update_schema(schema));
  1808. REQUIRE_THROWS(realm2->update_schema(remove_property(schema, "object", "value")));
  1809. }
  1810. SECTION("null migration callback should throw SchemaMismatchException") {
  1811. Schema new_schema = remove_property(schema, "object", "value");
  1812. REQUIRE_THROWS_AS(realm->update_schema(new_schema, 1, nullptr), SchemaMismatchException);
  1813. }
  1814. }