MigrationTests.mm 77 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2014 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. #import "RLMTestCase.h"
  19. #import "RLMMigration.h"
  20. #import "RLMObjectSchema_Private.hpp"
  21. #import "RLMObjectStore.h"
  22. #import "RLMObject_Private.h"
  23. #import "RLMProperty_Private.h"
  24. #import "RLMRealmConfiguration_Private.h"
  25. #import "RLMRealm_Dynamic.h"
  26. #import "RLMRealm_Private.hpp"
  27. #import "RLMSchema_Private.h"
  28. #import "RLMUtil.hpp"
  29. #import "RLMRealmUtil.hpp"
  30. #import "object_store.hpp"
  31. #import "shared_realm.hpp"
  32. #import <realm/table.hpp>
  33. #import <realm/version.hpp>
  34. #import <objc/runtime.h>
  35. using namespace realm;
  36. static void RLMAssertRealmSchemaMatchesTable(id self, RLMRealm *realm) {
  37. for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) {
  38. auto& info = realm->_info[objectSchema.className];
  39. TableRef table = ObjectStore::table_for_object_type(realm.group, objectSchema.objectName.UTF8String);
  40. for (RLMProperty *property in objectSchema.properties) {
  41. auto column = info.tableColumn(property);
  42. XCTAssertEqual(column, table->get_column_key(RLMStringDataWithNSString(property.columnName)));
  43. bool indexed = (property.indexed || property.isPrimary) && !(property.isPrimary && property.type == RLMPropertyTypeString);
  44. XCTAssertEqual(indexed, table->has_search_index(column));
  45. }
  46. }
  47. static_cast<void>(self);
  48. }
  49. @interface MigrationTestObject : RLMObject
  50. @property int intCol;
  51. @property NSString *stringCol;
  52. @end
  53. RLM_ARRAY_TYPE(MigrationTestObject);
  54. @implementation MigrationTestObject
  55. @end
  56. @interface MigrationPrimaryKeyObject : RLMObject
  57. @property int intCol;
  58. @end
  59. @implementation MigrationPrimaryKeyObject
  60. + (NSString *)primaryKey {
  61. return @"intCol";
  62. }
  63. @end
  64. @interface MigrationStringPrimaryKeyObject : RLMObject
  65. @property NSString * stringCol;
  66. @end
  67. @implementation MigrationStringPrimaryKeyObject
  68. + (NSString *)primaryKey {
  69. return @"stringCol";
  70. }
  71. @end
  72. @interface ThreeFieldMigrationTestObject : RLMObject
  73. @property int col1;
  74. @property int col2;
  75. @property int col3;
  76. @end
  77. @implementation ThreeFieldMigrationTestObject
  78. @end
  79. @interface MigrationTwoStringObject : RLMObject
  80. @property NSString *col1;
  81. @property NSString *col2;
  82. @end
  83. @implementation MigrationTwoStringObject
  84. @end
  85. @interface MigrationLinkObject : RLMObject
  86. @property MigrationTestObject *object;
  87. @property RLMArray<MigrationTestObject> *array;
  88. @end
  89. @implementation MigrationLinkObject
  90. @end
  91. @interface MigrationTests : RLMTestCase
  92. @end
  93. @interface DateMigrationObject : RLMObject
  94. @property (nonatomic, strong) NSDate *nonNullNonIndexed;
  95. @property (nonatomic, strong) NSDate *nullNonIndexed;
  96. @property (nonatomic, strong) NSDate *nonNullIndexed;
  97. @property (nonatomic, strong) NSDate *nullIndexed;
  98. @property (nonatomic) int cookie;
  99. @end
  100. #define RLM_OLD_DATE_FORMAT (REALM_VER_MAJOR < 1 && REALM_VER_MINOR < 100)
  101. @implementation DateMigrationObject
  102. + (NSArray *)requiredProperties {
  103. return @[@"nonNullNonIndexed", @"nonNullIndexed"];
  104. }
  105. + (NSArray *)indexedProperties {
  106. return @[@"nonNullIndexed", @"nullIndexed"];
  107. }
  108. @end
  109. @implementation MigrationTests
  110. #pragma mark - Helper methods
  111. - (RLMSchema *)schemaWithObjects:(NSArray *)objects {
  112. RLMSchema *schema = [[RLMSchema alloc] init];
  113. schema.objectSchema = objects;
  114. return schema;
  115. }
  116. - (RLMRealm *)realmWithSingleObject:(RLMObjectSchema *)objectSchema {
  117. return [self realmWithTestPathAndSchema:[self schemaWithObjects:@[objectSchema]]];
  118. }
  119. - (RLMRealmConfiguration *)config {
  120. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  121. config.fileURL = RLMTestRealmURL();
  122. return config;
  123. }
  124. - (void)createTestRealmWithClasses:(NSArray *)classes block:(void (^)(RLMRealm *realm))block {
  125. NSMutableArray *objectSchema = [NSMutableArray arrayWithCapacity:classes.count];
  126. for (Class cls in classes) {
  127. [objectSchema addObject:[RLMObjectSchema schemaForObjectClass:cls]];
  128. }
  129. [self createTestRealmWithSchema:objectSchema block:block];
  130. }
  131. - (void)createTestRealmWithSchema:(NSArray *)objectSchema block:(void (^)(RLMRealm *realm))block {
  132. @autoreleasepool {
  133. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  134. config.fileURL = RLMTestRealmURL();
  135. config.customSchema = [self schemaWithObjects:objectSchema];
  136. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  137. [realm beginWriteTransaction];
  138. block(realm);
  139. [realm commitWriteTransaction];
  140. }
  141. }
  142. - (RLMRealm *)migrateTestRealmWithBlock:(RLMMigrationBlock)block NS_RETURNS_RETAINED {
  143. @autoreleasepool {
  144. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  145. config.fileURL = RLMTestRealmURL();
  146. config.schemaVersion = 1;
  147. config.migrationBlock = block;
  148. XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
  149. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  150. RLMAssertRealmSchemaMatchesTable(self, realm);
  151. return realm;
  152. }
  153. }
  154. - (void)failToMigrateTestRealmWithBlock:(RLMMigrationBlock)block {
  155. @autoreleasepool {
  156. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  157. config.fileURL = RLMTestRealmURL();
  158. config.schemaVersion = 1;
  159. config.migrationBlock = block;
  160. XCTAssertFalse([RLMRealm performMigrationForConfiguration:config error:nil]);
  161. }
  162. }
  163. - (void)assertMigrationRequiredForChangeFrom:(NSArray *)from to:(NSArray *)to {
  164. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  165. config.customSchema = [self schemaWithObjects:from];
  166. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  167. config.customSchema = [self schemaWithObjects:to];
  168. config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  169. XCTFail(@"Migration block should not have been called");
  170. };
  171. RLMAssertThrowsWithCodeMatching([RLMRealm realmWithConfiguration:config error:nil], RLMErrorSchemaMismatch);
  172. __block bool migrationCalled = false;
  173. config.schemaVersion = 1;
  174. config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  175. migrationCalled = true;
  176. };
  177. XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
  178. XCTAssertTrue(migrationCalled);
  179. RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]);
  180. }
  181. - (void)assertNoMigrationRequiredForChangeFrom:(NSArray *)from to:(NSArray *)to {
  182. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  183. config.customSchema = [self schemaWithObjects:from];
  184. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  185. config.customSchema = [self schemaWithObjects:to];
  186. config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  187. XCTFail(@"Migration block should not have been called");
  188. };
  189. XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
  190. RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]);
  191. }
  192. - (RLMRealmConfiguration *)renameConfigurationWithObjectSchemas:(NSArray *)objectSchemas migrationBlock:(RLMMigrationBlock)block {
  193. RLMRealmConfiguration *configuration = [RLMRealmConfiguration new];
  194. configuration.fileURL = RLMTestRealmURL();
  195. configuration.schemaVersion = 1;
  196. configuration.customSchema = [self schemaWithObjects:objectSchemas];
  197. configuration.migrationBlock = block;
  198. return configuration;
  199. }
  200. - (RLMRealmConfiguration *)renameConfigurationWithObjectSchemas:(NSArray *)objectSchemas className:(NSString *)className
  201. oldName:(NSString *)oldName newName:(NSString *)newName {
  202. return [self renameConfigurationWithObjectSchemas:objectSchemas migrationBlock:^(RLMMigration *migration, uint64_t) {
  203. [migration renamePropertyForClass:className oldName:oldName newName:newName];
  204. [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  205. XCTAssertNotNil(oldObject[oldName]);
  206. RLMAssertThrowsWithReasonMatching(newObject[newName], @"Invalid property name");
  207. XCTAssertEqualObjects(oldObject[oldName], newObject[newName]);
  208. XCTAssertEqualObjects([oldObject.description stringByReplacingOccurrencesOfString:@"before_" withString:@""], newObject.description);
  209. }];
  210. }];
  211. }
  212. - (void)assertPropertyRenameError:(NSString *)errorMessage objectSchemas:(NSArray *)objectSchemas
  213. className:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName {
  214. RLMRealmConfiguration *config = [self renameConfigurationWithObjectSchemas:objectSchemas className:className
  215. oldName:oldName newName:newName];
  216. NSError *error;
  217. [RLMRealm performMigrationForConfiguration:config error:&error];
  218. XCTAssertTrue([error.localizedDescription rangeOfString:errorMessage].location != NSNotFound,
  219. @"\"%@\" should contain \"%@\"", error.localizedDescription, errorMessage);
  220. }
  221. - (void)assertPropertyRenameError:(NSString *)errorMessage
  222. firstSchemaTransform:(void (^)(RLMObjectSchema *, RLMProperty *, RLMProperty *))transform1
  223. secondSchemaTransform:(void (^)(RLMObjectSchema *, RLMProperty *, RLMProperty *))transform2 {
  224. RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
  225. RLMProperty *afterProperty = schema.properties.firstObject;
  226. RLMProperty *beforeProperty = [afterProperty copyWithNewName:@"before_stringCol"];
  227. schema.properties = @[beforeProperty];
  228. if (transform1) {
  229. transform1(schema, beforeProperty, afterProperty);
  230. }
  231. [self createTestRealmWithSchema:@[schema] block:^(RLMRealm *realm) {
  232. if (errorMessage == nil) {
  233. [StringObject createInRealm:realm withValue:@[@"0"]];
  234. }
  235. }];
  236. schema.properties = @[afterProperty];
  237. if (transform2) {
  238. transform2(schema, beforeProperty, afterProperty);
  239. }
  240. auto config = [self renameConfigurationWithObjectSchemas:@[schema]
  241. className:StringObject.className
  242. oldName:beforeProperty.name
  243. newName:afterProperty.name];
  244. if (errorMessage) {
  245. NSError *error;
  246. [RLMRealm performMigrationForConfiguration:config error:&error];
  247. XCTAssertEqualObjects([error localizedDescription], errorMessage);
  248. } else {
  249. XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
  250. XCTAssertEqualObjects(@"0", [[[StringObject allObjectsInRealm:[RLMRealm realmWithConfiguration:config error:nil]] firstObject] stringCol]);
  251. }
  252. }
  253. #pragma mark - Schema versions
  254. - (void)testGetSchemaVersion {
  255. XCTAssertThrows([RLMRealm schemaVersionAtURL:RLMDefaultRealmURL() encryptionKey:nil error:nil]);
  256. NSError *error;
  257. XCTAssertEqual(RLMNotVersioned, [RLMRealm schemaVersionAtURL:RLMDefaultRealmURL() encryptionKey:nil error:&error]);
  258. RLMValidateRealmError(error, RLMErrorFail, @"Cannot open an uninitialized realm in read-only mode", nil);
  259. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  260. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  261. XCTAssertEqual(0U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
  262. config.schemaVersion = 1;
  263. config.migrationBlock = ^(__unused RLMMigration *migration, uint64_t oldSchemaVersion) {
  264. XCTAssertEqual(0U, oldSchemaVersion);
  265. };
  266. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  267. XCTAssertEqual(1U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
  268. }
  269. - (void)testSchemaVersionCannotGoDown {
  270. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  271. config.schemaVersion = 10;
  272. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  273. XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
  274. config.schemaVersion = 5;
  275. RLMAssertThrowsWithReasonMatching([RLMRealm realmWithConfiguration:config error:nil],
  276. @"Provided schema version 5 is less than last set version 10.");
  277. }
  278. - (void)testDifferentSchemaVersionsAtDifferentPaths {
  279. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  280. config.schemaVersion = 10;
  281. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  282. XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
  283. RLMRealmConfiguration *config2 = [RLMRealmConfiguration defaultConfiguration];
  284. config2.schemaVersion = 5;
  285. config2.fileURL = RLMTestRealmURL();
  286. @autoreleasepool { [RLMRealm realmWithConfiguration:config2 error:nil]; }
  287. XCTAssertEqual(5U, [RLMRealm schemaVersionAtURL:config2.fileURL encryptionKey:nil error:nil]);
  288. // Should not have been changed
  289. XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
  290. }
  291. #pragma mark - Migration Requirements
  292. - (void)testAddingClassDoesNotRequireMigration {
  293. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  294. config.objectClasses = @[MigrationTestObject.class];
  295. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  296. config.objectClasses = @[MigrationTestObject.class, ThreeFieldMigrationTestObject.class];
  297. XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
  298. }
  299. - (void)testRemovingClassDoesNotRequireMigration {
  300. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  301. config.objectClasses = @[MigrationTestObject.class, ThreeFieldMigrationTestObject.class];
  302. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  303. config.objectClasses = @[MigrationTestObject.class];
  304. XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
  305. }
  306. - (void)testAddingColumnRequiresMigration {
  307. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  308. from.properties = [from.properties subarrayWithRange:{0, 1}];
  309. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  310. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  311. }
  312. - (void)testRemovingColumnRequiresMigration {
  313. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  314. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  315. to.properties = [to.properties subarrayWithRange:{0, 1}];
  316. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  317. }
  318. - (void)testChangingColumnOrderDoesNotRequireMigration {
  319. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  320. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  321. to.properties = @[to.properties[1], to.properties[0]];
  322. [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]];
  323. }
  324. - (void)testAddingIndexDoesNotRequireMigration {
  325. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  326. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  327. [to.properties[0] setIndexed:YES];
  328. [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]];
  329. }
  330. - (void)testRemovingIndexDoesNotRequireMigration {
  331. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  332. [from.properties[0] setIndexed:YES];
  333. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  334. [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]];
  335. }
  336. - (void)testAddingPrimaryKeyRequiresMigration {
  337. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  338. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  339. to.primaryKeyProperty = to.properties[0];
  340. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  341. }
  342. - (void)testRemovingPrimaryKeyRequiresMigration {
  343. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  344. from.primaryKeyProperty = from.properties[0];
  345. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  346. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  347. }
  348. - (void)testChangingPrimaryKeyRequiresMigration {
  349. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  350. from.primaryKeyProperty = from.properties[0];
  351. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  352. to.primaryKeyProperty = to.properties[1];
  353. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  354. }
  355. - (void)testMakingPropertyOptionalRequiresMigration {
  356. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  357. [from.properties[0] setOptional:NO];
  358. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  359. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  360. }
  361. - (void)testMakingPropertyNonOptionalRequiresMigration {
  362. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  363. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  364. [to.properties[0] setOptional:NO];
  365. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  366. }
  367. - (void)testChangingLinkTargetRequiresMigration {
  368. NSArray *linkTargets = @[[RLMObjectSchema schemaForObjectClass:MigrationTestObject.class],
  369. [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]];
  370. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
  371. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
  372. [to.properties[0] setObjectClassName:@"MigrationTwoStringObject"];
  373. [self assertMigrationRequiredForChangeFrom:[linkTargets arrayByAddingObject:from]
  374. to:[linkTargets arrayByAddingObject:to]];
  375. }
  376. - (void)testChangingLinkListTargetRequiresMigration {
  377. NSArray *linkTargets = @[[RLMObjectSchema schemaForObjectClass:MigrationTestObject.class],
  378. [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]];
  379. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
  380. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
  381. [to.properties[1] setObjectClassName:@"MigrationTwoStringObject"];
  382. [self assertMigrationRequiredForChangeFrom:[linkTargets arrayByAddingObject:from]
  383. to:[linkTargets arrayByAddingObject:to]];
  384. }
  385. - (void)testChangingPropertyTypesRequiresMigration {
  386. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  387. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  388. to.objectClass = RLMObject.class;
  389. RLMProperty *prop = to.properties[0];
  390. RLMProperty *strProp = to.properties[1];
  391. prop.type = strProp.type;
  392. [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
  393. }
  394. - (void)testDeleteRealmIfMigrationNeededWithSetCustomSchema {
  395. RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  396. from.properties = [from.properties subarrayWithRange:{0, 1}];
  397. RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
  398. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  399. config.customSchema = [self schemaWithObjects:@[from]];
  400. @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
  401. config.customSchema = [self schemaWithObjects:@[to]];
  402. config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  403. XCTFail(@"Migration block should not have been called");
  404. };
  405. config.deleteRealmIfMigrationNeeded = YES;
  406. XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
  407. RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]);
  408. }
  409. - (void)testDeleteRealmIfMigrationNeeded {
  410. for (uint64_t targetSchemaVersion = 1; targetSchemaVersion < 2; targetSchemaVersion++) {
  411. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  412. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  413. configuration.customSchema = [self schemaWithObjects:@[objectSchema]];
  414. @autoreleasepool {
  415. [[NSFileManager defaultManager] removeItemAtURL:configuration.fileURL error:nil];
  416. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  417. [realm transactionWithBlock:^{
  418. [realm addObject:[MigrationTestObject new]];
  419. }];
  420. }
  421. // Change string to int, requiring a migration
  422. objectSchema.objectClass = RLMObject.class;
  423. RLMProperty *stringCol = objectSchema.properties[1];
  424. stringCol.type = RLMPropertyTypeInt;
  425. stringCol.optional = NO;
  426. objectSchema.properties = @[stringCol];
  427. configuration.customSchema = [self schemaWithObjects:@[objectSchema]];
  428. @autoreleasepool {
  429. XCTAssertThrows([RLMRealm realmWithConfiguration:configuration error:nil]);
  430. RLMRealmConfiguration *dynamicConfiguration = [RLMRealmConfiguration defaultConfiguration];
  431. dynamicConfiguration.dynamic = YES;
  432. XCTAssertFalse([[RLMRealm realmWithConfiguration:dynamicConfiguration error:nil] isEmpty]);
  433. }
  434. configuration.schemaVersion = targetSchemaVersion;
  435. configuration.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  436. XCTFail(@"Migration block should not have been called");
  437. };
  438. configuration.deleteRealmIfMigrationNeeded = YES;
  439. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  440. RLMAssertRealmSchemaMatchesTable(self, realm);
  441. XCTAssertTrue(realm.isEmpty);
  442. }
  443. }
  444. #pragma mark - Allowed schema mismatches
  445. - (void)testMismatchedIndexAllowedForReadOnly {
  446. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
  447. [objectSchema.properties[0] setIndexed:YES];
  448. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *) { }];
  449. // should be able to open readonly with mismatched index schema
  450. RLMRealmConfiguration *config = [self config];
  451. config.readOnly = true;
  452. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  453. auto& info = realm->_info[@"StringObject"];
  454. XCTAssertTrue(info.table()->has_search_index(info.tableColumn(objectSchema.properties[0].name)));
  455. }
  456. - (void)testRearrangeProperties {
  457. // create object in default realm
  458. [RLMRealm.defaultRealm transactionWithBlock:^{
  459. [CircleObject createInDefaultRealmWithValue:@[@"data", NSNull.null]];
  460. }];
  461. // create realm with the properties reversed
  462. RLMSchema *schema = [[RLMSchema sharedSchema] copy];
  463. RLMObjectSchema *objectSchema = schema[@"CircleObject"];
  464. objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[0]];
  465. RLMRealm *realm = [self realmWithTestPathAndSchema:schema];
  466. [realm beginWriteTransaction];
  467. // -createObject:withValue: takes values in the order the properties appear in the array
  468. [realm createObject:CircleObject.className withValue:@[NSNull.null, @"data"]];
  469. RLMAssertThrowsWithReasonMatching(([realm createObject:CircleObject.className withValue:@[@"data", NSNull.null]]),
  470. @"Invalid value 'data' to initialize object of type 'CircleObject'");
  471. [realm commitWriteTransaction];
  472. // accessors should work
  473. CircleObject *obj = [[CircleObject allObjectsInRealm:realm] firstObject];
  474. XCTAssertEqualObjects(@"data", obj.data);
  475. XCTAssertNil(obj.next);
  476. [realm beginWriteTransaction];
  477. XCTAssertNoThrow(obj.data = @"new data");
  478. XCTAssertNoThrow(obj.next = obj);
  479. [realm commitWriteTransaction];
  480. // open the default Realm and make sure accessors with alternate ordering work
  481. CircleObject *defaultObj = [[CircleObject allObjects] firstObject];
  482. XCTAssertEqualObjects(defaultObj.data, @"data");
  483. RLMAssertRealmSchemaMatchesTable(self, realm);
  484. // re-check that things still work for the realm with the swapped order
  485. XCTAssertEqualObjects(obj.data, @"new data");
  486. [realm beginWriteTransaction];
  487. [realm createObject:CircleObject.className withValue:@[NSNull.null, @"data"]];
  488. RLMAssertThrowsWithReasonMatching(([realm createObject:CircleObject.className withValue:@[@"data", NSNull.null]]),
  489. @"Invalid value 'data' to initialize object of type 'CircleObject'");
  490. [realm commitWriteTransaction];
  491. }
  492. #pragma mark - Migration block invocatios
  493. - (void)testMigrationBlockNotCalledForIntialRealmCreation {
  494. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  495. config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  496. XCTFail(@"Migration block should not have been called");
  497. };
  498. XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
  499. }
  500. - (void)testMigrationBlockNotCalledWhenSchemaVersionIsUnchanged {
  501. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  502. config.schemaVersion = 1;
  503. @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
  504. config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  505. XCTFail(@"Migration block should not have been called");
  506. };
  507. @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
  508. @autoreleasepool { XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); }
  509. }
  510. - (void)testMigrationBlockCalledWhenSchemaVersionHasChanged {
  511. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  512. config.schemaVersion = 1;
  513. @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
  514. __block bool migrationCalled = false;
  515. config.schemaVersion = 2;
  516. config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  517. migrationCalled = true;
  518. };
  519. @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
  520. XCTAssertTrue(migrationCalled);
  521. migrationCalled = false;
  522. config.schemaVersion = 3;
  523. @autoreleasepool { XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); }
  524. XCTAssertTrue(migrationCalled);
  525. }
  526. #pragma mark - Async Migration
  527. - (void)testAsyncMigration {
  528. RLMRealmConfiguration *c = [RLMRealmConfiguration new];
  529. c.schemaVersion = 1;
  530. @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:c error:nil]); }
  531. XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  532. XCTestExpectation *ex = [self expectationWithDescription:@"async-migration"];
  533. __block bool migrationCalled = false;
  534. c.schemaVersion = 2;
  535. c.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  536. migrationCalled = true;
  537. };
  538. [RLMRealm asyncOpenWithConfiguration:c
  539. callbackQueue:dispatch_get_main_queue()
  540. callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
  541. XCTAssertTrue(migrationCalled);
  542. XCTAssertNil(error);
  543. XCTAssertNotNil(realm);
  544. [ex fulfill];
  545. }];
  546. XCTAssertFalse(migrationCalled);
  547. XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  548. [self waitForExpectationsWithTimeout:1 handler:nil];
  549. XCTAssertTrue(migrationCalled);
  550. XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  551. }
  552. #pragma mark - Migration Correctness
  553. - (void)testRemovingSubclass {
  554. RLMProperty *prop = [[RLMProperty alloc] initWithName:@"id"
  555. type:RLMPropertyTypeInt
  556. objectClassName:nil
  557. linkOriginPropertyName:nil
  558. indexed:NO
  559. optional:NO];
  560. RLMObjectSchema *objectSchema = [[RLMObjectSchema alloc] initWithClassName:@"DeletedClass" objectClass:RLMObject.class properties:@[prop]];
  561. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  562. [realm createObject:@"DeletedClass" withValue:@[@0]];
  563. }];
  564. // apply migration
  565. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  566. XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
  567. XCTAssertTrue([migration deleteDataForClassName:@"DeletedClass"]);
  568. XCTAssertFalse([migration deleteDataForClassName:@"NoSuchClass"]);
  569. XCTAssertFalse([migration deleteDataForClassName:self.nonLiteralNil]);
  570. [migration createObject:StringObject.className withValue:@[@"migration"]];
  571. XCTAssertTrue([migration deleteDataForClassName:StringObject.className]);
  572. }];
  573. XCTAssertFalse(ObjectStore::table_for_object_type(realm.group, "DeletedClass"), @"The deleted class should not have a table.");
  574. XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count);
  575. }
  576. - (void)testAddingPropertyAtEnd {
  577. // create schema to migrate from with single string column
  578. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  579. objectSchema.properties = @[objectSchema.properties[0]];
  580. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  581. [realm createObject:MigrationTestObject.className withValue:@[@1]];
  582. [realm createObject:MigrationTestObject.className withValue:@[@2]];
  583. }];
  584. // apply migration
  585. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  586. XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
  587. [migration enumerateObjects:MigrationTestObject.className
  588. block:^(RLMObject *oldObject, RLMObject *newObject) {
  589. XCTAssertThrows(oldObject[@"stringCol"], @"stringCol should not exist on old object");
  590. NSNumber *intObj;
  591. XCTAssertNoThrow(intObj = oldObject[@"intCol"], @"Should be able to access intCol on oldObject");
  592. XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]);
  593. NSString *stringObj = [NSString stringWithFormat:@"%@", intObj];
  594. XCTAssertNoThrow(newObject[@"stringCol"] = stringObj, @"Should be able to set stringCol");
  595. }];
  596. }];
  597. // verify migration
  598. MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
  599. XCTAssertEqual(mig1.intCol, 2, @"Int column should have value 2");
  600. XCTAssertEqualObjects(mig1.stringCol, @"2", @"String column should be populated");
  601. }
  602. - (void)testAddingPropertyAtBeginningPreservesData {
  603. // create schema to migrate from with the second and third columns from the final data
  604. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:ThreeFieldMigrationTestObject.class];
  605. objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[2]];
  606. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  607. [realm createObject:ThreeFieldMigrationTestObject.className withValue:@[@1, @2]];
  608. }];
  609. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  610. [migration enumerateObjects:ThreeFieldMigrationTestObject.className
  611. block:^(RLMObject *oldObject, RLMObject *newObject) {
  612. XCTAssertThrows(oldObject[@"col1"]);
  613. XCTAssertEqualObjects(oldObject[@"col2"], newObject[@"col2"]);
  614. XCTAssertEqualObjects(oldObject[@"col3"], newObject[@"col3"]);
  615. }];
  616. }];
  617. // verify migration
  618. ThreeFieldMigrationTestObject *mig = [ThreeFieldMigrationTestObject allObjectsInRealm:realm][0];
  619. XCTAssertEqual(0, mig.col1);
  620. XCTAssertEqual(1, mig.col2);
  621. XCTAssertEqual(2, mig.col3);
  622. }
  623. - (void)testRemoveProperty {
  624. // create schema with an extra column
  625. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  626. RLMProperty *thirdProperty = [[RLMProperty alloc] initWithName:@"deletedCol" type:RLMPropertyTypeBool objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO];
  627. objectSchema.properties = [objectSchema.properties arrayByAddingObject:thirdProperty];
  628. // create realm with old schema and populate
  629. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  630. [realm createObject:MigrationTestObject.className withValue:@[@1, @"1", @YES]];
  631. [realm createObject:MigrationTestObject.className withValue:@[@2, @"2", @NO]];
  632. }];
  633. // apply migration
  634. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  635. XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
  636. [migration enumerateObjects:MigrationTestObject.className
  637. block:^(RLMObject *oldObject, RLMObject *newObject) {
  638. XCTAssertNoThrow(oldObject[@"deletedCol"], @"Deleted column should be accessible on old object.");
  639. XCTAssertThrows(newObject[@"deletedCol"], @"Deleted column should not be accessible on new object.");
  640. XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]);
  641. XCTAssertEqualObjects(newObject[@"stringCol"], oldObject[@"stringCol"]);
  642. }];
  643. }];
  644. // verify migration
  645. MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
  646. XCTAssertThrows(mig1[@"deletedCol"], @"Deleted column should no longer be accessible.");
  647. }
  648. - (void)testRemoveAndAddProperty {
  649. // create schema to migrate from with single string column
  650. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  651. RLMProperty *oldInt = [[RLMProperty alloc] initWithName:@"oldIntCol" type:RLMPropertyTypeInt objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO];
  652. objectSchema.properties = @[oldInt, objectSchema.properties[1]];
  653. // create realm with old schema and populate
  654. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  655. [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]];
  656. [realm createObject:MigrationTestObject.className withValue:@[@1, @"2"]];
  657. }];
  658. // object migration object
  659. void (^migrateObjectBlock)(RLMObject *, RLMObject *) = ^(RLMObject *oldObject, RLMObject *newObject) {
  660. XCTAssertNoThrow(oldObject[@"oldIntCol"], @"Deleted column should be accessible on old object.");
  661. XCTAssertThrows(oldObject[@"intCol"], @"New column should not be accessible on old object.");
  662. XCTAssertEqual([oldObject[@"oldIntCol"] intValue], 1, @"Deleted column value is correct.");
  663. XCTAssertNoThrow(newObject[@"intCol"], @"New column is accessible on new object.");
  664. XCTAssertThrows(newObject[@"oldIntCol"], @"Old column should not be accessible on old object.");
  665. XCTAssertEqual([newObject[@"intCol"] intValue], 0, @"New column value is uninitialized.");
  666. };
  667. // apply migration
  668. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  669. XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
  670. [migration enumerateObjects:MigrationTestObject.className block:migrateObjectBlock];
  671. }];
  672. // verify migration
  673. MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
  674. XCTAssertThrows(mig1[@"oldIntCol"], @"Deleted column should no longer be accessible.");
  675. }
  676. - (void)testChangePropertyType {
  677. // make string an int
  678. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  679. objectSchema.objectClass = RLMObject.class;
  680. RLMProperty *stringCol = objectSchema.properties[1];
  681. stringCol.type = RLMPropertyTypeInt;
  682. stringCol.optional = NO;
  683. // create realm with old schema and populate
  684. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  685. [realm createObject:MigrationTestObject.className withValue:@[@1, @1]];
  686. [realm createObject:MigrationTestObject.className withValue:@[@2, @2]];
  687. }];
  688. // apply migration
  689. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  690. XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
  691. [migration enumerateObjects:MigrationTestObject.className
  692. block:^(RLMObject *oldObject, RLMObject *newObject) {
  693. XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]);
  694. NSNumber *intObj = oldObject[@"stringCol"];
  695. XCTAssert([intObj isKindOfClass:NSNumber.class], @"Old stringCol should be int");
  696. newObject[@"stringCol"] = intObj.stringValue;
  697. }];
  698. }];
  699. // verify migration
  700. MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
  701. XCTAssertEqualObjects(mig1[@"stringCol"], @"2", @"stringCol should be string after migration.");
  702. }
  703. - (void)testChangeObjectLinkType {
  704. // create realm with old schema and populate
  705. [self createTestRealmWithSchema:RLMSchema.sharedSchema.objectSchema block:^(RLMRealm *realm) {
  706. id obj = [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]];
  707. [realm createObject:MigrationLinkObject.className withValue:@[obj, @[obj]]];
  708. }];
  709. // Make the object link property link to a different class
  710. RLMRealmConfiguration *config = self.config;
  711. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
  712. [objectSchema.properties[0] setObjectClassName:MigrationLinkObject.className];
  713. config.customSchema = [self schemaWithObjects:@[objectSchema, [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]]];
  714. // Apply migration
  715. config.schemaVersion = 1;
  716. config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  717. XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
  718. [migration enumerateObjects:MigrationLinkObject.className
  719. block:^(RLMObject *oldObject, RLMObject *newObject) {
  720. XCTAssertNotNil(oldObject[@"object"]);
  721. XCTAssertNil(newObject[@"object"]);
  722. XCTAssertEqual(1U, [oldObject[@"array"] count]);
  723. XCTAssertEqual(1U, [newObject[@"array"] count]);
  724. }];
  725. };
  726. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  727. RLMAssertRealmSchemaMatchesTable(self, realm);
  728. }
  729. - (void)testChangeArrayLinkType {
  730. // create realm with old schema and populate
  731. RLMRealmConfiguration *config = [self config];
  732. [self createTestRealmWithSchema:RLMSchema.sharedSchema.objectSchema block:^(RLMRealm *realm) {
  733. id obj = [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]];
  734. [realm createObject:MigrationLinkObject.className withValue:@[obj, @[obj]]];
  735. }];
  736. // Make the array linklist property link to a different class
  737. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
  738. [objectSchema.properties[1] setObjectClassName:MigrationLinkObject.className];
  739. config.customSchema = [self schemaWithObjects:@[objectSchema, [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]]];
  740. // Apply migration
  741. config.schemaVersion = 1;
  742. config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  743. XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
  744. [migration enumerateObjects:MigrationLinkObject.className
  745. block:^(RLMObject *oldObject, RLMObject *newObject) {
  746. XCTAssertNotNil(oldObject[@"object"]);
  747. XCTAssertNotNil(newObject[@"object"]);
  748. XCTAssertEqual(1U, [oldObject[@"array"] count]);
  749. XCTAssertEqual(0U, [newObject[@"array"] count]);
  750. }];
  751. };
  752. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  753. RLMAssertRealmSchemaMatchesTable(self, realm);
  754. }
  755. - (void)testMakingPropertyPrimaryPreservesValues {
  756. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationStringPrimaryKeyObject.class];
  757. objectSchema.primaryKeyProperty = nil;
  758. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  759. [realm createObject:MigrationStringPrimaryKeyObject.className withValue:@[@"1"]];
  760. [realm createObject:MigrationStringPrimaryKeyObject.className withValue:@[@"2"]];
  761. }];
  762. RLMRealm *realm = [self migrateTestRealmWithBlock:nil];
  763. RLMResults *objects = [MigrationStringPrimaryKeyObject allObjectsInRealm:realm];
  764. XCTAssertEqualObjects(@"1", [objects[0] stringCol]);
  765. XCTAssertEqualObjects(@"2", [objects[1] stringCol]);
  766. }
  767. - (void)testAddingPrimaryKeyShouldRejectDuplicateValues {
  768. // make the pk non-primary so that we can add duplicate values
  769. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationPrimaryKeyObject.class];
  770. objectSchema.primaryKeyProperty = nil;
  771. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  772. // populate with values that will be invalid when the property is made primary
  773. [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
  774. [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
  775. }];
  776. // Fails due to duplicate values
  777. [self failToMigrateTestRealmWithBlock:nil];
  778. // apply good migration that deletes duplicates
  779. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  780. NSMutableSet *seen = [NSMutableSet set];
  781. __block bool duplicateDeleted = false;
  782. [migration enumerateObjects:@"MigrationPrimaryKeyObject" block:^(__unused RLMObject *oldObject, RLMObject *newObject) {
  783. if ([seen containsObject:newObject[@"intCol"]]) {
  784. duplicateDeleted = true;
  785. [migration deleteObject:newObject];
  786. }
  787. else {
  788. [seen addObject:newObject[@"intCol"]];
  789. }
  790. }];
  791. XCTAssertEqual(true, duplicateDeleted);
  792. }];
  793. // make sure deletion occurred
  794. XCTAssertEqual(1U, [[MigrationPrimaryKeyObject allObjectsInRealm:realm] count]);
  795. }
  796. - (void)testIncompleteMigrationIsRolledBack {
  797. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationPrimaryKeyObject.class];
  798. objectSchema.primaryKeyProperty = nil;
  799. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  800. [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
  801. [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
  802. }];
  803. // fail to apply migration
  804. [self failToMigrateTestRealmWithBlock:nil];
  805. // should still be able to open with pre-migration schema
  806. XCTAssertNoThrow([self realmWithSingleObject:objectSchema]);
  807. }
  808. - (void)testAddObjectDuringMigration {
  809. // initialize realm
  810. @autoreleasepool { [self realmWithTestPath]; }
  811. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration * migration, uint64_t) {
  812. [migration createObject:StringObject.className withValue:@[@"string"]];
  813. }];
  814. XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count);
  815. }
  816. - (void)testEnumeratedObjectsDuringMigration {
  817. [self createTestRealmWithClasses:@[StringObject.class, ArrayPropertyObject.class, IntObject.class] block:^(RLMRealm *realm) {
  818. [StringObject createInRealm:realm withValue:@[@"string"]];
  819. [ArrayPropertyObject createInRealm:realm withValue:@[@"array", @[@[@"string"]], @[@[@1]]]];
  820. }];
  821. RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  822. [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  823. XCTAssertEqualObjects([oldObject valueForKey:@"stringCol"], oldObject[@"stringCol"]);
  824. [newObject setValue:@"otherString" forKey:@"stringCol"];
  825. XCTAssertEqualObjects([oldObject valueForKey:@"realm"], oldObject.realm);
  826. XCTAssertThrows([oldObject valueForKey:@"noSuchKey"]);
  827. XCTAssertThrows([newObject setValue:@1 forKey:@"noSuchKey"]);
  828. }];
  829. [migration enumerateObjects:ArrayPropertyObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  830. XCTAssertEqual(RLMDynamicObject.class, newObject.class);
  831. XCTAssertEqual(RLMDynamicObject.class, oldObject.class);
  832. XCTAssertEqual(RLMDynamicObject.class, [[oldObject[@"array"] firstObject] class]);
  833. XCTAssertEqual(RLMDynamicObject.class, [[newObject[@"array"] firstObject] class]);
  834. }];
  835. }];
  836. XCTAssertEqualObjects(@"otherString", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
  837. }
  838. - (void)testEnumerateObjectsAfterDeleteObjects {
  839. [self createTestRealmWithClasses:@[StringObject.class, IntObject.class, BoolObject.class] block:^(RLMRealm *realm) {
  840. [StringObject createInRealm:realm withValue:@[@"1"]];
  841. [StringObject createInRealm:realm withValue:@[@"2"]];
  842. [StringObject createInRealm:realm withValue:@[@"3"]];
  843. [IntObject createInRealm:realm withValue:@[@1]];
  844. [IntObject createInRealm:realm withValue:@[@2]];
  845. [IntObject createInRealm:realm withValue:@[@3]];
  846. [BoolObject createInRealm:realm withValue:@[@YES]];
  847. [BoolObject createInRealm:realm withValue:@[@NO]];
  848. [BoolObject createInRealm:realm withValue:@[@YES]];
  849. }];
  850. [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  851. __block NSInteger count = 0;
  852. [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  853. XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
  854. if ([oldObject[@"stringCol"] isEqualToString:@"2"]) {
  855. [migration deleteObject:newObject];
  856. }
  857. }];
  858. [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  859. XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
  860. count++;
  861. }];
  862. XCTAssertEqual(count, 2);
  863. count = 0;
  864. [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  865. XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
  866. if ([oldObject[@"intCol"] isEqualToNumber:@1]) {
  867. [migration deleteObject:newObject];
  868. }
  869. }];
  870. [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  871. XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
  872. count++;
  873. }];
  874. XCTAssertEqual(count, 2);
  875. [migration enumerateObjects:BoolObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  876. XCTAssertEqualObjects(oldObject[@"boolCol"], newObject[@"boolCol"]);
  877. [migration deleteObject:newObject];
  878. }];
  879. [migration enumerateObjects:BoolObject.className block:^(__unused RLMObject *oldObject, __unused RLMObject *newObject) {
  880. XCTFail(@"This line should not executed since all objects have been deleted.");
  881. }];
  882. }];
  883. }
  884. - (void)testEnumerateObjectsAfterDeleteInsertObjects {
  885. [self createTestRealmWithClasses:@[StringObject.class, IntObject.class, BoolObject.class] block:^(RLMRealm *realm) {
  886. [StringObject createInRealm:realm withValue:@[@"1"]];
  887. [StringObject createInRealm:realm withValue:@[@"2"]];
  888. [StringObject createInRealm:realm withValue:@[@"3"]];
  889. [IntObject createInRealm:realm withValue:@[@1]];
  890. [IntObject createInRealm:realm withValue:@[@2]];
  891. [IntObject createInRealm:realm withValue:@[@3]];
  892. [BoolObject createInRealm:realm withValue:@[@YES]];
  893. [BoolObject createInRealm:realm withValue:@[@NO]];
  894. [BoolObject createInRealm:realm withValue:@[@YES]];
  895. }];
  896. [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  897. __block NSInteger count = 0;
  898. [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  899. XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
  900. if ([newObject[@"stringCol"] isEqualToString:@"2"]) {
  901. [migration deleteObject:newObject];
  902. [migration createObject:StringObject.className withValue:@[@"A"]];
  903. }
  904. }];
  905. [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  906. XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
  907. count++;
  908. }];
  909. XCTAssertEqual(count, 2);
  910. count = 0;
  911. [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  912. XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
  913. if ([newObject[@"intCol"] isEqualToNumber:@1]) {
  914. [migration deleteObject:newObject];
  915. [migration createObject:IntObject.className withValue:@[@0]];
  916. }
  917. }];
  918. [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  919. XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
  920. count++;
  921. }];
  922. XCTAssertEqual(count, 2);
  923. [migration enumerateObjects:BoolObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  924. XCTAssertEqualObjects(oldObject[@"boolCol"], newObject[@"boolCol"]);
  925. [migration deleteObject:newObject];
  926. [migration createObject:BoolObject.className withValue:@[@NO]];
  927. }];
  928. [migration enumerateObjects:BoolObject.className block:^(__unused RLMObject *oldObject, __unused RLMObject *newObject) {
  929. XCTFail(@"This line should not executed since all objects have been deleted.");
  930. }];
  931. }];
  932. }
  933. - (void)testEnumerateObjectsAfterDeleteDataForRemovedType {
  934. [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
  935. [IntObject createInRealm:realm withValue:@[@1]];
  936. [IntObject createInRealm:realm withValue:@[@2]];
  937. }];
  938. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  939. config.objectClasses = @[StringObject.class];
  940. config.fileURL = RLMTestRealmURL();
  941. config.schemaVersion = 1;
  942. config.migrationBlock = ^(RLMMigration *migration, uint64_t) {
  943. [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  944. XCTAssertNotNil(oldObject);
  945. XCTAssertNil(newObject);
  946. }];
  947. [migration deleteDataForClassName:IntObject.className];
  948. [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *) {
  949. XCTFail(@"should not have enumerated any objects");
  950. }];
  951. [migration deleteDataForClassName:IntObject.className];
  952. };
  953. XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
  954. }
  955. - (void)testEnumerateObjectsAfterDeleteData {
  956. [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
  957. [IntObject createInRealm:realm withValue:@[@1]];
  958. [IntObject createInRealm:realm withValue:@[@2]];
  959. }];
  960. [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  961. [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  962. XCTAssertNotNil(oldObject);
  963. XCTAssertNotNil(newObject);
  964. }];
  965. [migration deleteDataForClassName:IntObject.className];
  966. [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *) {
  967. XCTFail(@"should not have enumerated any objects");
  968. }];
  969. }];
  970. }
  971. - (RLMResults *)objectsOfType:(Class)cls {
  972. auto config = self.config;
  973. config.schemaVersion = 1;
  974. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  975. return [cls allObjectsInRealm:realm];
  976. }
  977. - (void)testDeleteSomeObjectsWithinMigration {
  978. [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
  979. [IntObject createInRealm:realm withValue:@[@1]];
  980. [IntObject createInRealm:realm withValue:@[@2]];
  981. [IntObject createInRealm:realm withValue:@[@3]];
  982. }];
  983. [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  984. [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
  985. if ([newObject[@"intCol"] intValue] != 2) {
  986. [migration deleteObject:newObject];
  987. }
  988. }];
  989. }];
  990. XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2]));
  991. }
  992. - (void)testDeleteObjectsWithinSeparateEnumerations {
  993. [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
  994. [IntObject createInRealm:realm withValue:@[@1]];
  995. [IntObject createInRealm:realm withValue:@[@2]];
  996. [IntObject createInRealm:realm withValue:@[@3]];
  997. }];
  998. [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  999. [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
  1000. if ([newObject[@"intCol"] intValue] == 1) {
  1001. [migration deleteObject:newObject];
  1002. }
  1003. }];
  1004. [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
  1005. if ([newObject[@"intCol"] intValue] == 3) {
  1006. [migration deleteObject:newObject];
  1007. }
  1008. }];
  1009. }];
  1010. XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2]));
  1011. }
  1012. - (void)testDeleteAndRecreateObjectsWithinMigration {
  1013. [self createTestRealmWithClasses:@[IntObject.class, PrimaryIntObject.class] block:^(RLMRealm *realm) {
  1014. [IntObject createInRealm:realm withValue:@[@1]];
  1015. [IntObject createInRealm:realm withValue:@[@2]];
  1016. [PrimaryIntObject createInRealm:realm withValue:@[@1]];
  1017. [PrimaryIntObject createInRealm:realm withValue:@[@2]];
  1018. }];
  1019. [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  1020. [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
  1021. [migration deleteObject:newObject];
  1022. }];
  1023. [migration enumerateObjects:PrimaryIntObject.className block:^(RLMObject *, RLMObject *newObject) {
  1024. [migration deleteObject:newObject];
  1025. }];
  1026. [migration createObject:IntObject.className withValue:@[@2]];
  1027. [migration createObject:IntObject.className withValue:@[@4]];
  1028. [migration createObject:PrimaryIntObject.className withValue:@[@2]];
  1029. [migration createObject:PrimaryIntObject.className withValue:@[@4]];
  1030. }];
  1031. XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
  1032. XCTAssertEqualObjects([[self objectsOfType:PrimaryIntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
  1033. }
  1034. - (void)testDeleteAllDataAndRecreateObjectsWithinMigration {
  1035. [self createTestRealmWithClasses:@[IntObject.class, PrimaryIntObject.class] block:^(RLMRealm *realm) {
  1036. [IntObject createInRealm:realm withValue:@[@1]];
  1037. [IntObject createInRealm:realm withValue:@[@2]];
  1038. [PrimaryIntObject createInRealm:realm withValue:@[@1]];
  1039. [PrimaryIntObject createInRealm:realm withValue:@[@2]];
  1040. }];
  1041. [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  1042. [migration deleteDataForClassName:IntObject.className];
  1043. [migration deleteDataForClassName:PrimaryIntObject.className];
  1044. [migration createObject:IntObject.className withValue:@[@2]];
  1045. [migration createObject:IntObject.className withValue:@[@4]];
  1046. [migration createObject:PrimaryIntObject.className withValue:@[@2]];
  1047. [migration createObject:PrimaryIntObject.className withValue:@[@4]];
  1048. }];
  1049. XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
  1050. XCTAssertEqualObjects([[self objectsOfType:PrimaryIntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
  1051. }
  1052. - (void)testRequiredToNullableAutoMigration {
  1053. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllOptionalTypes.class];
  1054. [objectSchema.properties setValue:@NO forKey:@"optional"];
  1055. // create initial required column
  1056. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  1057. [AllOptionalTypes createInRealm:realm withValue:@[@1, @1, @1, @1, @"str",
  1058. [@"data" dataUsingEncoding:NSUTF8StringEncoding],
  1059. [NSDate dateWithTimeIntervalSince1970:1]]];
  1060. [AllOptionalTypes createInRealm:realm withValue:@[@2, @2, @2, @0, @"str2",
  1061. [@"data2" dataUsingEncoding:NSUTF8StringEncoding],
  1062. [NSDate dateWithTimeIntervalSince1970:2]]];
  1063. }];
  1064. RLMRealm *realm = [self migrateTestRealmWithBlock:nil];
  1065. RLMResults *allObjects = [AllOptionalTypes allObjectsInRealm:realm];
  1066. XCTAssertEqual(2U, allObjects.count);
  1067. AllOptionalTypes *obj = allObjects[0];
  1068. XCTAssertEqualObjects(@1, obj.intObj);
  1069. XCTAssertEqualObjects(@1, obj.floatObj);
  1070. XCTAssertEqualObjects(@1, obj.doubleObj);
  1071. XCTAssertEqualObjects(@1, obj.boolObj);
  1072. XCTAssertEqualObjects(@"str", obj.string);
  1073. XCTAssertEqualObjects([@"data" dataUsingEncoding:NSUTF8StringEncoding], obj.data);
  1074. XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:1], obj.date);
  1075. obj = allObjects[1];
  1076. XCTAssertEqualObjects(@2, obj.intObj);
  1077. XCTAssertEqualObjects(@2, obj.floatObj);
  1078. XCTAssertEqualObjects(@2, obj.doubleObj);
  1079. XCTAssertEqualObjects(@0, obj.boolObj);
  1080. XCTAssertEqualObjects(@"str2", obj.string);
  1081. XCTAssertEqualObjects([@"data2" dataUsingEncoding:NSUTF8StringEncoding], obj.data);
  1082. XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:2], obj.date);
  1083. }
  1084. - (void)testNullableToRequiredMigration {
  1085. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllOptionalTypes.class];
  1086. // create initial nullable column
  1087. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  1088. [AllOptionalTypes createInRealm:realm withValue:@[ [NSNull null], [NSNull null], [NSNull null], [NSNull null],
  1089. [NSNull null], [NSNull null], [NSNull null]]];
  1090. [AllOptionalTypes createInRealm:realm withValue:@[@2, @2, @2, @0, @"str2",
  1091. [@"data2" dataUsingEncoding:NSUTF8StringEncoding],
  1092. [NSDate dateWithTimeIntervalSince1970:2]]];
  1093. }];
  1094. objectSchema.objectClass = RLMObject.class;
  1095. [objectSchema.properties setValue:@NO forKey:@"optional"];
  1096. RLMRealm *realm;
  1097. @autoreleasepool {
  1098. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  1099. config.fileURL = RLMTestRealmURL();
  1100. config.customSchema = [self schemaWithObjects:@[ objectSchema ]];
  1101. config.schemaVersion = 1;
  1102. XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
  1103. realm = [RLMRealm realmWithConfiguration:config error:nil];
  1104. RLMAssertRealmSchemaMatchesTable(self, realm);
  1105. }
  1106. RLMResults *allObjects = [AllOptionalTypes allObjectsInRealm:realm];
  1107. XCTAssertEqual(2U, allObjects.count);
  1108. AllOptionalTypes *obj = allObjects[0];
  1109. XCTAssertEqualObjects(@0, obj[@"intObj"]);
  1110. XCTAssertEqualObjects(@0, obj[@"floatObj"]);
  1111. XCTAssertEqualObjects(@0, obj[@"doubleObj"]);
  1112. XCTAssertEqualObjects(@0, obj[@"boolObj"]);
  1113. XCTAssertEqualObjects(@"", obj[@"string"]);
  1114. XCTAssertEqualObjects(NSData.data, obj[@"data"]);
  1115. XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:0], obj[@"date"]);
  1116. obj = allObjects[1];
  1117. XCTAssertEqualObjects(@0, obj[@"intObj"]);
  1118. XCTAssertEqualObjects(@0, obj[@"floatObj"]);
  1119. XCTAssertEqualObjects(@0, obj[@"doubleObj"]);
  1120. XCTAssertEqualObjects(@0, obj[@"boolObj"]);
  1121. XCTAssertEqualObjects(@"", obj[@"string"]);
  1122. XCTAssertEqualObjects(NSData.data, obj[@"data"]);
  1123. XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:0], obj[@"date"]);
  1124. }
  1125. - (void)testMigrationAfterReorderingProperties {
  1126. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:RequiredPropertiesObject.class];
  1127. // Create a table where the order of columns does not match the order the properties are declared in the class.
  1128. objectSchema.properties = @[ objectSchema.properties[2], objectSchema.properties[0], objectSchema.properties[1] ];
  1129. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  1130. // We use a dictionary here to ensure that the test reaches the migration case below, even if the non-migration
  1131. // case doesn't handle the ordering correctly. The non-migration case is tested in testRearrangeProperties.
  1132. [RequiredPropertiesObject createInRealm:realm withValue:@{ @"stringCol": @"Hello", @"dateCol": [NSDate date], @"binaryCol": [NSData data] }];
  1133. }];
  1134. objectSchema = [RLMObjectSchema schemaForObjectClass:RequiredPropertiesObject.class];
  1135. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  1136. config.fileURL = RLMTestRealmURL();
  1137. config.customSchema = [self schemaWithObjects:@[objectSchema]];
  1138. config.schemaVersion = 1;
  1139. config.migrationBlock = ^(RLMMigration *migration, uint64_t) {
  1140. [migration createObject:RequiredPropertiesObject.className withValue:@[@"World", [NSData data], [NSDate date]]];
  1141. };
  1142. XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
  1143. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  1144. RLMResults *allObjects = [RequiredPropertiesObject allObjectsInRealm:realm];
  1145. XCTAssertEqualObjects(@"Hello", [allObjects[0] stringCol]);
  1146. XCTAssertEqualObjects(@"World", [allObjects[1] stringCol]);
  1147. }
  1148. - (void)testModifyPrimaryKeyInMigration {
  1149. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:PrimaryStringObject.class];
  1150. objectSchema.primaryKeyProperty = objectSchema[@"intCol"];
  1151. [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
  1152. for (int i = 0; i < 10; ++i) {
  1153. [PrimaryStringObject createInRealm:realm withValue:@[@(i).stringValue, @(i + 10)]];
  1154. }
  1155. }];
  1156. auto realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
  1157. [migration enumerateObjects:@"PrimaryStringObject" block:^(RLMObject *oldObject, RLMObject *newObject) {
  1158. newObject[@"stringCol"] = [oldObject[@"intCol"] stringValue];
  1159. }];
  1160. }];
  1161. for (int i = 10; i < 20; ++i) {
  1162. auto obj = [PrimaryStringObject objectInRealm:realm forPrimaryKey:@(i).stringValue];
  1163. XCTAssertNotNil(obj);
  1164. XCTAssertEqual(obj.intCol, i);
  1165. }
  1166. }
  1167. #pragma mark - Property Rename
  1168. // Successful Property Rename Tests
  1169. - (void)testMigrationRenameProperty {
  1170. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllTypesObject.class];
  1171. RLMObjectSchema *stringObjectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
  1172. RLMObjectSchema *linkingObjectsSchema = [RLMObjectSchema schemaForObjectClass:LinkToAllTypesObject.class];
  1173. NSMutableArray *beforeProperties = [NSMutableArray arrayWithCapacity:objectSchema.properties.count];
  1174. for (RLMProperty *property in objectSchema.properties) {
  1175. [beforeProperties addObject:[property copyWithNewName:[NSString stringWithFormat:@"before_%@", property.name]]];
  1176. }
  1177. NSArray *afterProperties = objectSchema.properties;
  1178. objectSchema.properties = beforeProperties;
  1179. NSDate *now = [NSDate dateWithTimeIntervalSince1970:100000];
  1180. id inputValue = @[@YES, @1, @1.1f, @1.11, @"string", [NSData dataWithBytes:"a" length:1], now, @YES, @11, @[@"a"]];
  1181. [self createTestRealmWithSchema:@[objectSchema, stringObjectSchema, linkingObjectsSchema] block:^(RLMRealm *realm) {
  1182. [AllTypesObject createInRealm:realm withValue:inputValue];
  1183. }];
  1184. objectSchema.properties = afterProperties;
  1185. RLMRealmConfiguration *config = [self renameConfigurationWithObjectSchemas:@[objectSchema, stringObjectSchema, linkingObjectsSchema]
  1186. migrationBlock:^(RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
  1187. [afterProperties enumerateObjectsUsingBlock:^(RLMProperty *property, NSUInteger idx, __unused BOOL *stop) {
  1188. [migration renamePropertyForClass:AllTypesObject.className oldName:[beforeProperties[idx] name] newName:property.name];
  1189. [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  1190. XCTAssertNotNil(oldObject[[beforeProperties[idx] name]]);
  1191. RLMAssertThrowsWithReasonMatching(newObject[[beforeProperties[idx] name]], @"Invalid property name");
  1192. if (![property.objectClassName isEqualToString:@""]) { return; }
  1193. XCTAssertEqualObjects(oldObject[[beforeProperties[idx] name]], newObject[property.name]);
  1194. }];
  1195. }];
  1196. [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  1197. XCTAssertEqualObjects([oldObject.description stringByReplacingOccurrencesOfString:@"before_" withString:@""], newObject.description);
  1198. }];
  1199. }];
  1200. XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
  1201. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
  1202. RLMAssertRealmSchemaMatchesTable(self, realm);
  1203. RLMResults *allObjects = [AllTypesObject allObjectsInRealm:realm];
  1204. XCTAssertEqual(1U, allObjects.count);
  1205. XCTAssertEqual(1U, [[StringObject allObjectsInRealm:realm] count]);
  1206. AllTypesObject *obj = allObjects.firstObject;
  1207. XCTAssertEqualObjects(inputValue[0], @(obj.boolCol));
  1208. XCTAssertEqualObjects(inputValue[1], @(obj.intCol));
  1209. XCTAssertEqualObjects(inputValue[2], @(obj.floatCol));
  1210. XCTAssertEqualObjects(inputValue[3], @(obj.doubleCol));
  1211. XCTAssertEqualObjects(inputValue[4], obj.stringCol);
  1212. XCTAssertEqualObjects(inputValue[5], obj.binaryCol);
  1213. XCTAssertEqualObjects(inputValue[6], obj.dateCol);
  1214. XCTAssertEqualObjects(inputValue[7], @(obj.cBoolCol));
  1215. XCTAssertEqualObjects(inputValue[8], @(obj.longCol));
  1216. XCTAssertEqualObjects(inputValue[9], @[obj.objectCol.stringCol]);
  1217. }
  1218. - (void)testMultipleMigrationRenameProperty {
  1219. RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
  1220. schema.properties = @[[schema.properties.firstObject copyWithNewName:@"stringCol0"]];
  1221. [self createTestRealmWithSchema:@[schema] block:^(RLMRealm *realm) {
  1222. [StringObject createInRealm:realm withValue:@[@"0"]];
  1223. }];
  1224. schema.properties = @[[schema.properties.firstObject copyWithNewName:@"stringCol"]];
  1225. __block bool migrationCalled = false;
  1226. RLMRealmConfiguration *config = [RLMRealmConfiguration new];
  1227. config.fileURL = RLMTestRealmURL();
  1228. config.customSchema = [self schemaWithObjects:@[schema]];
  1229. config.schemaVersion = 2;
  1230. config.migrationBlock = ^(RLMMigration *migration, uint64_t oldVersion){
  1231. migrationCalled = true;
  1232. __block id oldValue = nil;
  1233. if (oldVersion < 1) {
  1234. [migration renamePropertyForClass:StringObject.className oldName:@"stringCol0" newName:@"stringCol1"];
  1235. [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  1236. oldValue = oldObject[@"stringCol0"];
  1237. XCTAssertNotNil(oldValue);
  1238. RLMAssertThrowsWithReasonMatching(newObject[@"stringCol0"], @"Invalid property name");
  1239. RLMAssertThrowsWithReasonMatching(newObject[@"stringCol1"], @"Invalid property name");
  1240. }];
  1241. }
  1242. if (oldVersion < 2) {
  1243. [migration renamePropertyForClass:StringObject.className oldName:@"stringCol1" newName:@"stringCol"];
  1244. [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
  1245. XCTAssertEqualObjects(oldObject[@"stringCol0"], oldValue);
  1246. XCTAssertEqualObjects(newObject[@"stringCol"], oldValue);
  1247. RLMAssertThrowsWithReasonMatching(newObject[@"stringCol0"], @"Invalid property name");
  1248. RLMAssertThrowsWithReasonMatching(newObject[@"stringCol1"], @"Invalid property name");
  1249. }];
  1250. }
  1251. };
  1252. XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
  1253. XCTAssertTrue(migrationCalled);
  1254. XCTAssertEqualObjects(@"0", [[[StringObject allObjectsInRealm:[RLMRealm realmWithConfiguration:config error:nil]] firstObject] stringCol]);
  1255. }
  1256. - (void)testMigrationRenamePropertyPrimaryKeyBoth {
  1257. [self assertPropertyRenameError:nil firstSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
  1258. schema.primaryKeyProperty = beforeProperty;
  1259. } secondSchemaTransform:^(RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) {
  1260. schema.primaryKeyProperty = afterProperty;
  1261. }];
  1262. }
  1263. - (void)testMigrationRenamePropertyUnsetPrimaryKey {
  1264. [self assertPropertyRenameError:nil firstSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
  1265. schema.primaryKeyProperty = beforeProperty;
  1266. } secondSchemaTransform:^(RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
  1267. schema.primaryKeyProperty = nil;
  1268. }];
  1269. }
  1270. - (void)testMigrationRenamePropertySetPrimaryKey {
  1271. [self assertPropertyRenameError:nil firstSchemaTransform:nil
  1272. secondSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *, RLMProperty *afterProperty) {
  1273. schema.primaryKeyProperty = afterProperty;
  1274. }];
  1275. }
  1276. - (void)testMigrationRenamePropertyIndexBoth {
  1277. [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
  1278. afterProperty.indexed = YES;
  1279. beforeProperty.indexed = YES;
  1280. } secondSchemaTransform:nil];
  1281. }
  1282. - (void)testMigrationRenamePropertyUnsetIndex {
  1283. [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
  1284. beforeProperty.indexed = YES;
  1285. } secondSchemaTransform:nil];
  1286. }
  1287. - (void)testMigrationRenamePropertySetIndex {
  1288. [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) {
  1289. afterProperty.indexed = YES;
  1290. } secondSchemaTransform:nil];
  1291. }
  1292. - (void)testMigrationRenamePropertySetOptional {
  1293. [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
  1294. beforeProperty.optional = NO;
  1295. } secondSchemaTransform:nil];
  1296. }
  1297. // Unsuccessful Property Rename Tests
  1298. - (void)testMigrationRenamePropertySetRequired {
  1299. [self assertPropertyRenameError:@"Cannot rename property 'StringObject.before_stringCol' to 'stringCol' because it would change from optional to required."
  1300. firstSchemaTransform:^(__unused RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) {
  1301. afterProperty.optional = NO;
  1302. } secondSchemaTransform:nil];
  1303. }
  1304. - (void)testMigrationRenamePropertyTypeMismatch {
  1305. [self assertPropertyRenameError:@"Cannot rename property 'StringObject.before_stringCol' to 'stringCol' because it would change from type 'int' to 'string'."
  1306. firstSchemaTransform:^(RLMObjectSchema *, RLMProperty *beforeProperty, RLMProperty *) {
  1307. beforeProperty.type = RLMPropertyTypeInt;
  1308. } secondSchemaTransform:nil];
  1309. }
  1310. - (void)testMigrationRenamePropertyObjectTypeMismatch {
  1311. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
  1312. RLMObjectSchema *migrationObjectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
  1313. NSArray *afterProperties = objectSchema.properties;
  1314. NSMutableArray *beforeProperties = [NSMutableArray arrayWithCapacity:2];
  1315. for (RLMProperty *property in afterProperties) {
  1316. RLMProperty *beforeProperty = [property copyWithNewName:[NSString stringWithFormat:@"before_%@", property.name]];
  1317. beforeProperty.objectClassName = MigrationLinkObject.className;
  1318. [beforeProperties addObject:beforeProperty];
  1319. }
  1320. objectSchema.properties = beforeProperties;
  1321. [self createTestRealmWithSchema:@[objectSchema] block:^(__unused RLMRealm *realm) {
  1322. // No need to create an object
  1323. }];
  1324. objectSchema.properties = afterProperties;
  1325. [self assertPropertyRenameError:@"Cannot rename property 'MigrationLinkObject.before_object' to 'object' because it would change from type '<MigrationLinkObject>' to '<MigrationTestObject>'."
  1326. objectSchemas:@[objectSchema, migrationObjectSchema]
  1327. className:MigrationLinkObject.className
  1328. oldName:[beforeProperties[0] name]
  1329. newName:[afterProperties[0] name]];
  1330. [self assertPropertyRenameError:@"Cannot rename property 'MigrationLinkObject.before_array' to 'array' because it would change from type 'array<MigrationLinkObject>' to 'array<MigrationTestObject>'."
  1331. objectSchemas:@[objectSchema, migrationObjectSchema]
  1332. className:MigrationLinkObject.className
  1333. oldName:[beforeProperties[1] name]
  1334. newName:[afterProperties[1] name]];
  1335. }
  1336. - (void)testMigrationRenameMissingPropertiesAndClasses {
  1337. RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
  1338. [self createTestRealmWithSchema:@[objectSchema] block:^(__unused RLMRealm *realm) {
  1339. // No need to create an object
  1340. }];
  1341. // Missing Old Property
  1342. [self assertPropertyRenameError:@"Cannot rename property 'StringObject.nonExistentProperty1' because it does not exist."
  1343. objectSchemas:@[objectSchema] className:StringObject.className
  1344. oldName:@"nonExistentProperty1" newName:@"nonExistentProperty2"];
  1345. // Missing New Property
  1346. RLMObjectSchema *renamedProperty = [objectSchema copy];
  1347. renamedProperty.properties[0].name = @"stringCol2";
  1348. [self assertPropertyRenameError:@"Renamed property 'StringObject.nonExistentProperty' does not exist."
  1349. objectSchemas:@[renamedProperty] className:StringObject.className
  1350. oldName:@"stringCol" newName:@"nonExistentProperty"];
  1351. // Removed Class
  1352. [self assertPropertyRenameError:@"Cannot rename properties for type 'StringObject' because it has been removed from the Realm."
  1353. objectSchemas:@[[RLMObjectSchema schemaForObjectClass:IntObject.class]]
  1354. className:StringObject.className oldName:@"stringCol" newName:@"stringCol2"];
  1355. // Without Removing Old Property
  1356. RLMProperty *secondProperty = [objectSchema.properties.firstObject copyWithNewName:@"stringCol2"];
  1357. objectSchema.properties = [objectSchema.properties arrayByAddingObject:secondProperty];
  1358. [self assertPropertyRenameError:@"Cannot rename property 'StringObject.stringCol' to 'stringCol2' because the source property still exists."
  1359. objectSchemas:@[objectSchema] className:StringObject.className oldName:@"stringCol" newName:@"stringCol2"];
  1360. }
  1361. @end