MigrationTests.mm 84 KB

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