12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2014 Realm Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- ////////////////////////////////////////////////////////////////////////////
- #import "RLMTestCase.h"
- #import "RLMMigration.h"
- #import "RLMObjectSchema_Private.hpp"
- #import "RLMObjectStore.h"
- #import "RLMObject_Private.h"
- #import "RLMProperty_Private.h"
- #import "RLMRealmConfiguration_Private.h"
- #import "RLMRealm_Dynamic.h"
- #import "RLMRealm_Private.hpp"
- #import "RLMSchema_Private.h"
- #import "RLMUtil.hpp"
- #import "RLMRealmUtil.hpp"
- #import "object_store.hpp"
- #import "shared_realm.hpp"
- #import <realm/table.hpp>
- #import <realm/version.hpp>
- #import <objc/runtime.h>
- using namespace realm;
- static void RLMAssertRealmSchemaMatchesTable(id self, RLMRealm *realm) {
- for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) {
- auto& info = realm->_info[objectSchema.className];
- TableRef table = ObjectStore::table_for_object_type(realm.group, objectSchema.objectName.UTF8String);
- for (RLMProperty *property in objectSchema.properties) {
- auto column = info.tableColumn(property);
- XCTAssertEqual(column, table->get_column_index(RLMStringDataWithNSString(property.columnName)));
- XCTAssertEqual(property.indexed || property.isPrimary, table->has_search_index(column));
- }
- }
- }
- @interface MigrationTestObject : RLMObject
- @property int intCol;
- @property NSString *stringCol;
- @end
- RLM_ARRAY_TYPE(MigrationTestObject);
- @implementation MigrationTestObject
- @end
- @interface MigrationPrimaryKeyObject : RLMObject
- @property int intCol;
- @end
- @implementation MigrationPrimaryKeyObject
- + (NSString *)primaryKey {
- return @"intCol";
- }
- @end
- @interface MigrationStringPrimaryKeyObject : RLMObject
- @property NSString * stringCol;
- @end
- @implementation MigrationStringPrimaryKeyObject
- + (NSString *)primaryKey {
- return @"stringCol";
- }
- @end
- @interface ThreeFieldMigrationTestObject : RLMObject
- @property int col1;
- @property int col2;
- @property int col3;
- @end
- @implementation ThreeFieldMigrationTestObject
- @end
- @interface MigrationTwoStringObject : RLMObject
- @property NSString *col1;
- @property NSString *col2;
- @end
- @implementation MigrationTwoStringObject
- @end
- @interface MigrationLinkObject : RLMObject
- @property MigrationTestObject *object;
- @property RLMArray<MigrationTestObject> *array;
- @end
- @implementation MigrationLinkObject
- @end
- @interface MigrationTests : RLMTestCase
- @end
- @interface DateMigrationObject : RLMObject
- @property (nonatomic, strong) NSDate *nonNullNonIndexed;
- @property (nonatomic, strong) NSDate *nullNonIndexed;
- @property (nonatomic, strong) NSDate *nonNullIndexed;
- @property (nonatomic, strong) NSDate *nullIndexed;
- @property (nonatomic) int cookie;
- @end
- #define RLM_OLD_DATE_FORMAT (REALM_VER_MAJOR < 1 && REALM_VER_MINOR < 100)
- @implementation DateMigrationObject
- + (NSArray *)requiredProperties {
- return @[@"nonNullNonIndexed", @"nonNullIndexed"];
- }
- + (NSArray *)indexedProperties {
- return @[@"nonNullIndexed", @"nullIndexed"];
- }
- @end
- @implementation MigrationTests
- #pragma mark - Helper methods
- - (RLMSchema *)schemaWithObjects:(NSArray *)objects {
- RLMSchema *schema = [[RLMSchema alloc] init];
- schema.objectSchema = objects;
- return schema;
- }
- - (RLMRealm *)realmWithSingleObject:(RLMObjectSchema *)objectSchema {
- return [self realmWithTestPathAndSchema:[self schemaWithObjects:@[objectSchema]]];
- }
- - (RLMRealmConfiguration *)config {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.fileURL = RLMTestRealmURL();
- return config;
- }
- - (void)createTestRealmWithClasses:(NSArray *)classes block:(void (^)(RLMRealm *realm))block {
- NSMutableArray *objectSchema = [NSMutableArray arrayWithCapacity:classes.count];
- for (Class cls in classes) {
- [objectSchema addObject:[RLMObjectSchema schemaForObjectClass:cls]];
- }
- [self createTestRealmWithSchema:objectSchema block:block];
- }
- - (void)createTestRealmWithSchema:(NSArray *)objectSchema block:(void (^)(RLMRealm *realm))block {
- @autoreleasepool {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.fileURL = RLMTestRealmURL();
- config.customSchema = [self schemaWithObjects:objectSchema];
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- [realm beginWriteTransaction];
- block(realm);
- [realm commitWriteTransaction];
- }
- }
- - (RLMRealm *)migrateTestRealmWithBlock:(RLMMigrationBlock)block NS_RETURNS_RETAINED {
- @autoreleasepool {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.fileURL = RLMTestRealmURL();
- config.schemaVersion = 1;
- config.migrationBlock = block;
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMAssertRealmSchemaMatchesTable(self, realm);
- return realm;
- }
- }
- - (void)failToMigrateTestRealmWithBlock:(RLMMigrationBlock)block {
- @autoreleasepool {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.fileURL = RLMTestRealmURL();
- config.schemaVersion = 1;
- config.migrationBlock = block;
- XCTAssertFalse([RLMRealm performMigrationForConfiguration:config error:nil]);
- }
- }
- - (void)assertMigrationRequiredForChangeFrom:(NSArray *)from to:(NSArray *)to {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.customSchema = [self schemaWithObjects:from];
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- config.customSchema = [self schemaWithObjects:to];
- config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- XCTFail(@"Migration block should not have been called");
- };
- RLMAssertThrowsWithCodeMatching([RLMRealm realmWithConfiguration:config error:nil], RLMErrorSchemaMismatch);
- __block bool migrationCalled = false;
- config.schemaVersion = 1;
- config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- migrationCalled = true;
- };
- XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
- XCTAssertTrue(migrationCalled);
- RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]);
- }
- - (void)assertNoMigrationRequiredForChangeFrom:(NSArray *)from to:(NSArray *)to {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.customSchema = [self schemaWithObjects:from];
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- config.customSchema = [self schemaWithObjects:to];
- config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- XCTFail(@"Migration block should not have been called");
- };
- XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
- RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]);
- }
- - (RLMRealmConfiguration *)renameConfigurationWithObjectSchemas:(NSArray *)objectSchemas migrationBlock:(RLMMigrationBlock)block {
- RLMRealmConfiguration *configuration = [RLMRealmConfiguration new];
- configuration.fileURL = RLMTestRealmURL();
- configuration.schemaVersion = 1;
- configuration.customSchema = [self schemaWithObjects:objectSchemas];
- configuration.migrationBlock = block;
- return configuration;
- }
- - (RLMRealmConfiguration *)renameConfigurationWithObjectSchemas:(NSArray *)objectSchemas className:(NSString *)className
- oldName:(NSString *)oldName newName:(NSString *)newName {
- return [self renameConfigurationWithObjectSchemas:objectSchemas migrationBlock:^(RLMMigration *migration, uint64_t) {
- [migration renamePropertyForClass:className oldName:oldName newName:newName];
- [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNotNil(oldObject[oldName]);
- RLMAssertThrowsWithReasonMatching(newObject[newName], @"Invalid property name");
- XCTAssertEqualObjects(oldObject[oldName], newObject[newName]);
- XCTAssertEqualObjects([oldObject.description stringByReplacingOccurrencesOfString:@"before_" withString:@""], newObject.description);
- }];
- }];
- }
- - (void)assertPropertyRenameError:(NSString *)errorMessage objectSchemas:(NSArray *)objectSchemas
- className:(NSString *)className oldName:(NSString *)oldName newName:(NSString *)newName {
- RLMRealmConfiguration *config = [self renameConfigurationWithObjectSchemas:objectSchemas className:className
- oldName:oldName newName:newName];
- NSError *error;
- [RLMRealm performMigrationForConfiguration:config error:&error];
- XCTAssertTrue([error.localizedDescription rangeOfString:errorMessage].location != NSNotFound,
- @"\"%@\" should contain \"%@\"", error.localizedDescription, errorMessage);
- }
- - (void)assertPropertyRenameError:(NSString *)errorMessage
- firstSchemaTransform:(void (^)(RLMObjectSchema *, RLMProperty *, RLMProperty *))transform1
- secondSchemaTransform:(void (^)(RLMObjectSchema *, RLMProperty *, RLMProperty *))transform2 {
- RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
- RLMProperty *afterProperty = schema.properties.firstObject;
- RLMProperty *beforeProperty = [afterProperty copyWithNewName:@"before_stringCol"];
- schema.properties = @[beforeProperty];
- if (transform1) { transform1(schema, beforeProperty, afterProperty); }
- [self createTestRealmWithSchema:@[schema] block:^(RLMRealm *realm) {
- if (errorMessage == nil) {
- [StringObject createInRealm:realm withValue:@[@"0"]];
- }
- }];
- schema.properties = @[afterProperty];
- if (transform2) { transform2(schema, beforeProperty, afterProperty); }
- RLMRealmConfiguration *config = [self renameConfigurationWithObjectSchemas:@[schema] className:StringObject.className
- oldName:beforeProperty.name newName:afterProperty.name];
- if (errorMessage) {
- NSError *error;
- [RLMRealm performMigrationForConfiguration:config error:&error];
- XCTAssertEqualObjects([error localizedDescription], errorMessage);
- } else {
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- XCTAssertEqualObjects(@"0", [[[StringObject allObjectsInRealm:[RLMRealm realmWithConfiguration:config error:nil]] firstObject] stringCol]);
- }
- }
- #pragma mark - Schema versions
- - (void)testGetSchemaVersion {
- XCTAssertThrows([RLMRealm schemaVersionAtURL:RLMDefaultRealmURL() encryptionKey:nil error:nil]);
- NSError *error;
- XCTAssertEqual(RLMNotVersioned, [RLMRealm schemaVersionAtURL:RLMDefaultRealmURL() encryptionKey:nil error:&error]);
- RLMValidateRealmError(error, RLMErrorFail, @"Cannot open an uninitialized realm in read-only mode", nil);
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- XCTAssertEqual(0U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
- config.schemaVersion = 1;
- config.migrationBlock = ^(__unused RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(0U, oldSchemaVersion);
- };
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- XCTAssertEqual(1U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
- }
- - (void)testSchemaVersionCannotGoDown {
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.schemaVersion = 10;
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
- config.schemaVersion = 5;
- RLMAssertThrowsWithReasonMatching([RLMRealm realmWithConfiguration:config error:nil],
- @"Provided schema version 5 is less than last set version 10.");
- }
- - (void)testDifferentSchemaVersionsAtDifferentPaths {
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.schemaVersion = 10;
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
- RLMRealmConfiguration *config2 = [RLMRealmConfiguration defaultConfiguration];
- config2.schemaVersion = 5;
- config2.fileURL = RLMTestRealmURL();
- @autoreleasepool { [RLMRealm realmWithConfiguration:config2 error:nil]; }
- XCTAssertEqual(5U, [RLMRealm schemaVersionAtURL:config2.fileURL encryptionKey:nil error:nil]);
- // Should not have been changed
- XCTAssertEqual(10U, [RLMRealm schemaVersionAtURL:config.fileURL encryptionKey:nil error:nil]);
- }
- #pragma mark - Migration Requirements
- - (void)testAddingClassDoesNotRequireMigration {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.objectClasses = @[MigrationTestObject.class];
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- config.objectClasses = @[MigrationTestObject.class, ThreeFieldMigrationTestObject.class];
- XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
- }
- - (void)testRemovingClassDoesNotRequireMigration {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.objectClasses = @[MigrationTestObject.class, ThreeFieldMigrationTestObject.class];
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- config.objectClasses = @[MigrationTestObject.class];
- XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
- }
- - (void)testAddingColumnRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- from.properties = [from.properties subarrayWithRange:{0, 1}];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testRemovingColumnRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- to.properties = [to.properties subarrayWithRange:{0, 1}];
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testChangingColumnOrderDoesNotRequireMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- to.properties = @[to.properties[1], to.properties[0]];
- [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testAddingIndexDoesNotRequireMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [to.properties[0] setIndexed:YES];
- [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testRemovingIndexDoesNotRequireMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [from.properties[0] setIndexed:YES];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [self assertNoMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testAddingPrimaryKeyRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- to.primaryKeyProperty = to.properties[0];
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testRemovingPrimaryKeyRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- from.primaryKeyProperty = from.properties[0];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testChangingPrimaryKeyRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- from.primaryKeyProperty = from.properties[0];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- to.primaryKeyProperty = to.properties[1];
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testMakingPropertyOptionalRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [from.properties[0] setOptional:NO];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testMakingPropertyNonOptionalRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- [to.properties[0] setOptional:NO];
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testChangingLinkTargetRequiresMigration {
- NSArray *linkTargets = @[[RLMObjectSchema schemaForObjectClass:MigrationTestObject.class],
- [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]];
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
- [to.properties[0] setObjectClassName:@"MigrationTwoStringObject"];
- [self assertMigrationRequiredForChangeFrom:[linkTargets arrayByAddingObject:from]
- to:[linkTargets arrayByAddingObject:to]];
- }
- - (void)testChangingLinkListTargetRequiresMigration {
- NSArray *linkTargets = @[[RLMObjectSchema schemaForObjectClass:MigrationTestObject.class],
- [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class]];
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
- [to.properties[1] setObjectClassName:@"MigrationTwoStringObject"];
- [self assertMigrationRequiredForChangeFrom:[linkTargets arrayByAddingObject:from]
- to:[linkTargets arrayByAddingObject:to]];
- }
- - (void)testChangingPropertyTypesRequiresMigration {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- to.objectClass = RLMObject.class;
- RLMProperty *prop = to.properties[0];
- RLMProperty *strProp = to.properties[1];
- prop.type = strProp.type;
- [self assertMigrationRequiredForChangeFrom:@[from] to:@[to]];
- }
- - (void)testDeleteRealmIfMigrationNeededWithSetCustomSchema {
- RLMObjectSchema *from = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- from.properties = [from.properties subarrayWithRange:{0, 1}];
- RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationTwoStringObject.class];
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.customSchema = [self schemaWithObjects:@[from]];
- @autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
- config.customSchema = [self schemaWithObjects:@[to]];
- config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- XCTFail(@"Migration block should not have been called");
- };
- config.deleteRealmIfMigrationNeeded = YES;
- XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
- RLMAssertRealmSchemaMatchesTable(self, [RLMRealm realmWithConfiguration:config error:nil]);
- }
- - (void)testDeleteRealmIfMigrationNeeded {
- for (uint64_t targetSchemaVersion = 1; targetSchemaVersion < 2; targetSchemaVersion++) {
- RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- configuration.customSchema = [self schemaWithObjects:@[objectSchema]];
- @autoreleasepool {
- [[NSFileManager defaultManager] removeItemAtURL:configuration.fileURL error:nil];
- RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
- [realm transactionWithBlock:^{
- [realm addObject:[MigrationTestObject new]];
- }];
- }
- // Change string to int, requiring a migration
- objectSchema.objectClass = RLMObject.class;
- RLMProperty *stringCol = objectSchema.properties[1];
- stringCol.type = RLMPropertyTypeInt;
- stringCol.optional = NO;
- objectSchema.properties = @[stringCol];
- configuration.customSchema = [self schemaWithObjects:@[objectSchema]];
- @autoreleasepool {
- XCTAssertThrows([RLMRealm realmWithConfiguration:configuration error:nil]);
- RLMRealmConfiguration *dynamicConfiguration = [RLMRealmConfiguration defaultConfiguration];
- dynamicConfiguration.dynamic = YES;
- XCTAssertFalse([[RLMRealm realmWithConfiguration:dynamicConfiguration error:nil] isEmpty]);
- }
- configuration.schemaVersion = targetSchemaVersion;
- configuration.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- XCTFail(@"Migration block should not have been called");
- };
- configuration.deleteRealmIfMigrationNeeded = YES;
- RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
- RLMAssertRealmSchemaMatchesTable(self, realm);
- XCTAssertTrue(realm.isEmpty);
- }
- }
- #pragma mark - Allowed schema mismatches
- - (void)testMismatchedIndexAllowedForReadOnly {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
- [objectSchema.properties[0] setIndexed:YES];
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *) { }];
- // should be able to open readonly with mismatched index schema
- RLMRealmConfiguration *config = [self config];
- config.readOnly = true;
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- auto& info = realm->_info[@"StringObject"];
- XCTAssertTrue(info.table()->has_search_index(info.tableColumn(objectSchema.properties[0].name)));
- }
- - (void)testRearrangeProperties {
- // create object in default realm
- [RLMRealm.defaultRealm transactionWithBlock:^{
- [CircleObject createInDefaultRealmWithValue:@[@"data", NSNull.null]];
- }];
- // create realm with the properties reversed
- RLMSchema *schema = [[RLMSchema sharedSchema] copy];
- RLMObjectSchema *objectSchema = schema[@"CircleObject"];
- objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[0]];
- RLMRealm *realm = [self realmWithTestPathAndSchema:schema];
- [realm beginWriteTransaction];
- // -createObject:withValue: takes values in the order the properties appear in the array
- [realm createObject:CircleObject.className withValue:@[NSNull.null, @"data"]];
- RLMAssertThrowsWithReasonMatching(([realm createObject:CircleObject.className withValue:@[@"data", NSNull.null]]),
- @"Invalid value 'data' to initialize object of type 'CircleObject'");
- [realm commitWriteTransaction];
- // accessors should work
- CircleObject *obj = [[CircleObject allObjectsInRealm:realm] firstObject];
- XCTAssertEqualObjects(@"data", obj.data);
- XCTAssertNil(obj.next);
- [realm beginWriteTransaction];
- XCTAssertNoThrow(obj.data = @"new data");
- XCTAssertNoThrow(obj.next = obj);
- [realm commitWriteTransaction];
- // open the default Realm and make sure accessors with alternate ordering work
- CircleObject *defaultObj = [[CircleObject allObjects] firstObject];
- XCTAssertEqualObjects(defaultObj.data, @"data");
- RLMAssertRealmSchemaMatchesTable(self, realm);
- // re-check that things still work for the realm with the swapped order
- XCTAssertEqualObjects(obj.data, @"new data");
- [realm beginWriteTransaction];
- [realm createObject:CircleObject.className withValue:@[NSNull.null, @"data"]];
- RLMAssertThrowsWithReasonMatching(([realm createObject:CircleObject.className withValue:@[@"data", NSNull.null]]),
- @"Invalid value 'data' to initialize object of type 'CircleObject'");
- [realm commitWriteTransaction];
- }
- #pragma mark - Migration block invocatios
- - (void)testMigrationBlockNotCalledForIntialRealmCreation {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- XCTFail(@"Migration block should not have been called");
- };
- XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
- }
- - (void)testMigrationBlockNotCalledWhenSchemaVersionIsUnchanged {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.schemaVersion = 1;
- @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
- config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- XCTFail(@"Migration block should not have been called");
- };
- @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
- @autoreleasepool { XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); }
- }
- - (void)testMigrationBlockCalledWhenSchemaVersionHasChanged {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.schemaVersion = 1;
- @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
- __block bool migrationCalled = false;
- config.schemaVersion = 2;
- config.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- migrationCalled = true;
- };
- @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]); }
- XCTAssertTrue(migrationCalled);
- migrationCalled = false;
- config.schemaVersion = 3;
- @autoreleasepool { XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]); }
- XCTAssertTrue(migrationCalled);
- }
- #pragma mark - Async Migration
- - (void)testAsyncMigration {
- RLMRealmConfiguration *c = [RLMRealmConfiguration new];
- c.schemaVersion = 1;
- @autoreleasepool { XCTAssertNoThrow([RLMRealm realmWithConfiguration:c error:nil]); }
- XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- XCTestExpectation *ex = [self expectationWithDescription:@"async-migration"];
- __block bool migrationCalled = false;
- c.schemaVersion = 2;
- c.migrationBlock = ^(__unused RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- migrationCalled = true;
- };
- [RLMRealm asyncOpenWithConfiguration:c
- callbackQueue:dispatch_get_main_queue()
- callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
- XCTAssertTrue(migrationCalled);
- XCTAssertNil(error);
- XCTAssertNotNil(realm);
- [ex fulfill];
- }];
- XCTAssertFalse(migrationCalled);
- XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- [self waitForExpectationsWithTimeout:1 handler:nil];
- XCTAssertTrue(migrationCalled);
- XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- }
- #pragma mark - Migration Correctness
- - (void)testRemovingSubclass {
- RLMProperty *prop = [[RLMProperty alloc] initWithName:@"id"
- type:RLMPropertyTypeInt
- objectClassName:nil
- linkOriginPropertyName:nil
- indexed:NO
- optional:NO];
- RLMObjectSchema *objectSchema = [[RLMObjectSchema alloc] initWithClassName:@"DeletedClass" objectClass:RLMObject.class properties:@[prop]];
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:@"DeletedClass" withValue:@[@0]];
- }];
- // apply migration
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
- XCTAssertTrue([migration deleteDataForClassName:@"DeletedClass"]);
- XCTAssertFalse([migration deleteDataForClassName:@"NoSuchClass"]);
- XCTAssertFalse([migration deleteDataForClassName:self.nonLiteralNil]);
- [migration createObject:StringObject.className withValue:@[@"migration"]];
- XCTAssertTrue([migration deleteDataForClassName:StringObject.className]);
- }];
- XCTAssertFalse(ObjectStore::table_for_object_type(realm.group, "DeletedClass"), @"The deleted class should not have a table.");
- XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count);
- }
- - (void)testAddingPropertyAtEnd {
- // create schema to migrate from with single string column
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- objectSchema.properties = @[objectSchema.properties[0]];
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:MigrationTestObject.className withValue:@[@1]];
- [realm createObject:MigrationTestObject.className withValue:@[@2]];
- }];
- // apply migration
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
- [migration enumerateObjects:MigrationTestObject.className
- block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertThrows(oldObject[@"stringCol"], @"stringCol should not exist on old object");
- NSNumber *intObj;
- XCTAssertNoThrow(intObj = oldObject[@"intCol"], @"Should be able to access intCol on oldObject");
- XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]);
- NSString *stringObj = [NSString stringWithFormat:@"%@", intObj];
- XCTAssertNoThrow(newObject[@"stringCol"] = stringObj, @"Should be able to set stringCol");
- }];
- }];
- // verify migration
- MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
- XCTAssertEqual(mig1.intCol, 2, @"Int column should have value 2");
- XCTAssertEqualObjects(mig1.stringCol, @"2", @"String column should be populated");
- }
- - (void)testAddingPropertyAtBeginningPreservesData {
- // create schema to migrate from with the second and third columns from the final data
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:ThreeFieldMigrationTestObject.class];
- objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[2]];
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:ThreeFieldMigrationTestObject.className withValue:@[@1, @2]];
- }];
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:ThreeFieldMigrationTestObject.className
- block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertThrows(oldObject[@"col1"]);
- XCTAssertEqualObjects(oldObject[@"col2"], newObject[@"col2"]);
- XCTAssertEqualObjects(oldObject[@"col3"], newObject[@"col3"]);
- }];
- }];
- // verify migration
- ThreeFieldMigrationTestObject *mig = [ThreeFieldMigrationTestObject allObjectsInRealm:realm][0];
- XCTAssertEqual(0, mig.col1);
- XCTAssertEqual(1, mig.col2);
- XCTAssertEqual(2, mig.col3);
- }
- - (void)testRemoveProperty {
- // create schema with an extra column
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- RLMProperty *thirdProperty = [[RLMProperty alloc] initWithName:@"deletedCol" type:RLMPropertyTypeBool objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO];
- objectSchema.properties = [objectSchema.properties arrayByAddingObject:thirdProperty];
- // create realm with old schema and populate
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:MigrationTestObject.className withValue:@[@1, @"1", @YES]];
- [realm createObject:MigrationTestObject.className withValue:@[@2, @"2", @NO]];
- }];
- // apply migration
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
- [migration enumerateObjects:MigrationTestObject.className
- block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNoThrow(oldObject[@"deletedCol"], @"Deleted column should be accessible on old object.");
- XCTAssertThrows(newObject[@"deletedCol"], @"Deleted column should not be accessible on new object.");
- XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]);
- XCTAssertEqualObjects(newObject[@"stringCol"], oldObject[@"stringCol"]);
- }];
- }];
- // verify migration
- MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
- XCTAssertThrows(mig1[@"deletedCol"], @"Deleted column should no longer be accessible.");
- }
- - (void)testRemoveAndAddProperty {
- // create schema to migrate from with single string column
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- RLMProperty *oldInt = [[RLMProperty alloc] initWithName:@"oldIntCol" type:RLMPropertyTypeInt objectClassName:nil linkOriginPropertyName:nil indexed:NO optional:NO];
- objectSchema.properties = @[oldInt, objectSchema.properties[1]];
- // create realm with old schema and populate
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]];
- [realm createObject:MigrationTestObject.className withValue:@[@1, @"2"]];
- }];
- // object migration object
- void (^migrateObjectBlock)(RLMObject *, RLMObject *) = ^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNoThrow(oldObject[@"oldIntCol"], @"Deleted column should be accessible on old object.");
- XCTAssertThrows(oldObject[@"intCol"], @"New column should not be accessible on old object.");
- XCTAssertEqual([oldObject[@"oldIntCol"] intValue], 1, @"Deleted column value is correct.");
- XCTAssertNoThrow(newObject[@"intCol"], @"New column is accessible on new object.");
- XCTAssertThrows(newObject[@"oldIntCol"], @"Old column should not be accessible on old object.");
- XCTAssertEqual([newObject[@"intCol"] intValue], 0, @"New column value is uninitialized.");
- };
- // apply migration
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
- [migration enumerateObjects:MigrationTestObject.className block:migrateObjectBlock];
- }];
- // verify migration
- MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
- XCTAssertThrows(mig1[@"oldIntCol"], @"Deleted column should no longer be accessible.");
- }
- - (void)testChangePropertyType {
- // make string an int
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- objectSchema.objectClass = RLMObject.class;
- RLMProperty *stringCol = objectSchema.properties[1];
- stringCol.type = RLMPropertyTypeInt;
- stringCol.optional = NO;
- // create realm with old schema and populate
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:MigrationTestObject.className withValue:@[@1, @1]];
- [realm createObject:MigrationTestObject.className withValue:@[@2, @2]];
- }];
- // apply migration
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
- [migration enumerateObjects:MigrationTestObject.className
- block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(newObject[@"intCol"], oldObject[@"intCol"]);
- NSNumber *intObj = oldObject[@"stringCol"];
- XCTAssert([intObj isKindOfClass:NSNumber.class], @"Old stringCol should be int");
- newObject[@"stringCol"] = intObj.stringValue;
- }];
- }];
- // verify migration
- MigrationTestObject *mig1 = [MigrationTestObject allObjectsInRealm:realm][1];
- XCTAssertEqualObjects(mig1[@"stringCol"], @"2", @"stringCol should be string after migration.");
- }
- - (void)testChangeObjectLinkType {
- // create realm with old schema and populate
- [self createTestRealmWithSchema:RLMSchema.sharedSchema.objectSchema block:^(RLMRealm *realm) {
- id obj = [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]];
- [realm createObject:MigrationLinkObject.className withValue:@[obj, @[obj]]];
- }];
- // Make the object link property link to a different class
- RLMRealmConfiguration *config = self.config;
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
- [objectSchema.properties[0] setObjectClassName:MigrationLinkObject.className];
- config.customSchema = [self schemaWithObjects:@[objectSchema, [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]]];
- // Apply migration
- config.schemaVersion = 1;
- config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
- [migration enumerateObjects:MigrationLinkObject.className
- block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNotNil(oldObject[@"object"]);
- XCTAssertNil(newObject[@"object"]);
- XCTAssertEqual(1U, [oldObject[@"array"] count]);
- XCTAssertEqual(1U, [newObject[@"array"] count]);
- }];
- };
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMAssertRealmSchemaMatchesTable(self, realm);
- }
- - (void)testChangeArrayLinkType {
- // create realm with old schema and populate
- RLMRealmConfiguration *config = [self config];
- [self createTestRealmWithSchema:RLMSchema.sharedSchema.objectSchema block:^(RLMRealm *realm) {
- id obj = [realm createObject:MigrationTestObject.className withValue:@[@1, @"1"]];
- [realm createObject:MigrationLinkObject.className withValue:@[obj, @[obj]]];
- }];
- // Make the array linklist property link to a different class
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
- [objectSchema.properties[1] setObjectClassName:MigrationLinkObject.className];
- config.customSchema = [self schemaWithObjects:@[objectSchema, [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class]]];
- // Apply migration
- config.schemaVersion = 1;
- config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
- XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0");
- [migration enumerateObjects:MigrationLinkObject.className
- block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNotNil(oldObject[@"object"]);
- XCTAssertNotNil(newObject[@"object"]);
- XCTAssertEqual(1U, [oldObject[@"array"] count]);
- XCTAssertEqual(0U, [newObject[@"array"] count]);
- }];
- };
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMAssertRealmSchemaMatchesTable(self, realm);
- }
- - (void)testMakingPropertyPrimaryPreservesValues {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationStringPrimaryKeyObject.class];
- objectSchema.primaryKeyProperty = nil;
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:MigrationStringPrimaryKeyObject.className withValue:@[@"1"]];
- [realm createObject:MigrationStringPrimaryKeyObject.className withValue:@[@"2"]];
- }];
- RLMRealm *realm = [self migrateTestRealmWithBlock:nil];
- RLMResults *objects = [MigrationStringPrimaryKeyObject allObjectsInRealm:realm];
- XCTAssertEqualObjects(@"1", [objects[0] stringCol]);
- XCTAssertEqualObjects(@"2", [objects[1] stringCol]);
- }
- - (void)testAddingPrimaryKeyShouldRejectDuplicateValues {
- // make the pk non-primary so that we can add duplicate values
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationPrimaryKeyObject.class];
- objectSchema.primaryKeyProperty = nil;
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- // populate with values that will be invalid when the property is made primary
- [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
- [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
- }];
- // Fails due to duplicate values
- [self failToMigrateTestRealmWithBlock:nil];
- // apply good migration that deletes duplicates
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- NSMutableSet *seen = [NSMutableSet set];
- __block bool duplicateDeleted = false;
- [migration enumerateObjects:@"MigrationPrimaryKeyObject" block:^(__unused RLMObject *oldObject, RLMObject *newObject) {
- if ([seen containsObject:newObject[@"intCol"]]) {
- duplicateDeleted = true;
- [migration deleteObject:newObject];
- }
- else {
- [seen addObject:newObject[@"intCol"]];
- }
- }];
- XCTAssertEqual(true, duplicateDeleted);
- }];
- // make sure deletion occurred
- XCTAssertEqual(1U, [[MigrationPrimaryKeyObject allObjectsInRealm:realm] count]);
- }
- - (void)testIncompleteMigrationIsRolledBack {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationPrimaryKeyObject.class];
- objectSchema.primaryKeyProperty = nil;
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
- [realm createObject:MigrationPrimaryKeyObject.className withValue:@[@1]];
- }];
- // fail to apply migration
- [self failToMigrateTestRealmWithBlock:nil];
- // should still be able to open with pre-migration schema
- XCTAssertNoThrow([self realmWithSingleObject:objectSchema]);
- }
- - (void)testAddObjectDuringMigration {
- // initialize realm
- @autoreleasepool { [self realmWithTestPath]; }
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration * migration, uint64_t) {
- [migration createObject:StringObject.className withValue:@[@"string"]];
- }];
- XCTAssertEqual(1U, [StringObject allObjectsInRealm:realm].count);
- }
- - (void)testEnumeratedObjectsDuringMigration {
- [self createTestRealmWithClasses:@[StringObject.class, ArrayPropertyObject.class, IntObject.class] block:^(RLMRealm *realm) {
- [StringObject createInRealm:realm withValue:@[@"string"]];
- [ArrayPropertyObject createInRealm:realm withValue:@[@"array", @[@[@"string"]], @[@[@1]]]];
- }];
- RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects([oldObject valueForKey:@"stringCol"], oldObject[@"stringCol"]);
- [newObject setValue:@"otherString" forKey:@"stringCol"];
- XCTAssertEqualObjects([oldObject valueForKey:@"realm"], oldObject.realm);
- XCTAssertThrows([oldObject valueForKey:@"noSuchKey"]);
- XCTAssertThrows([newObject setValue:@1 forKey:@"noSuchKey"]);
- }];
- [migration enumerateObjects:ArrayPropertyObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqual(RLMDynamicObject.class, newObject.class);
- XCTAssertEqual(RLMDynamicObject.class, oldObject.class);
- XCTAssertEqual(RLMDynamicObject.class, [[oldObject[@"array"] firstObject] class]);
- XCTAssertEqual(RLMDynamicObject.class, [[newObject[@"array"] firstObject] class]);
- }];
- }];
- XCTAssertEqualObjects(@"otherString", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
- }
- - (void)testEnumerateObjectsAfterDeleteObjects {
- [self createTestRealmWithClasses:@[StringObject.class, IntObject.class, BoolObject.class] block:^(RLMRealm *realm) {
- [StringObject createInRealm:realm withValue:@[@"1"]];
- [StringObject createInRealm:realm withValue:@[@"2"]];
- [StringObject createInRealm:realm withValue:@[@"3"]];
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- [IntObject createInRealm:realm withValue:@[@3]];
- [BoolObject createInRealm:realm withValue:@[@YES]];
- [BoolObject createInRealm:realm withValue:@[@NO]];
- [BoolObject createInRealm:realm withValue:@[@YES]];
- }];
- [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- __block NSInteger count = 0;
- [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
- if ([oldObject[@"stringCol"] isEqualToString:@"2"]) {
- [migration deleteObject:newObject];
- }
- }];
- [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
- count++;
- }];
- XCTAssertEqual(count, 2);
- count = 0;
- [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
- if ([oldObject[@"intCol"] isEqualToNumber:@1]) {
- [migration deleteObject:newObject];
- }
- }];
- [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
- count++;
- }];
- XCTAssertEqual(count, 2);
- [migration enumerateObjects:BoolObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"boolCol"], newObject[@"boolCol"]);
- [migration deleteObject:newObject];
- }];
- [migration enumerateObjects:BoolObject.className block:^(__unused RLMObject *oldObject, __unused RLMObject *newObject) {
- XCTFail(@"This line should not executed since all objects have been deleted.");
- }];
- }];
- }
- - (void)testEnumerateObjectsAfterDeleteInsertObjects {
- [self createTestRealmWithClasses:@[StringObject.class, IntObject.class, BoolObject.class] block:^(RLMRealm *realm) {
- [StringObject createInRealm:realm withValue:@[@"1"]];
- [StringObject createInRealm:realm withValue:@[@"2"]];
- [StringObject createInRealm:realm withValue:@[@"3"]];
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- [IntObject createInRealm:realm withValue:@[@3]];
- [BoolObject createInRealm:realm withValue:@[@YES]];
- [BoolObject createInRealm:realm withValue:@[@NO]];
- [BoolObject createInRealm:realm withValue:@[@YES]];
- }];
- [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- __block NSInteger count = 0;
- [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
- if ([newObject[@"stringCol"] isEqualToString:@"2"]) {
- [migration deleteObject:newObject];
- [migration createObject:StringObject.className withValue:@[@"A"]];
- }
- }];
- [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"stringCol"], newObject[@"stringCol"]);
- count++;
- }];
- XCTAssertEqual(count, 2);
- count = 0;
- [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
- if ([newObject[@"intCol"] isEqualToNumber:@1]) {
- [migration deleteObject:newObject];
- [migration createObject:IntObject.className withValue:@[@0]];
- }
- }];
- [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"intCol"], newObject[@"intCol"]);
- count++;
- }];
- XCTAssertEqual(count, 2);
- [migration enumerateObjects:BoolObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"boolCol"], newObject[@"boolCol"]);
- [migration deleteObject:newObject];
- [migration createObject:BoolObject.className withValue:@[@NO]];
- }];
- [migration enumerateObjects:BoolObject.className block:^(__unused RLMObject *oldObject, __unused RLMObject *newObject) {
- XCTFail(@"This line should not executed since all objects have been deleted.");
- }];
- }];
- }
- - (void)testEnumerateObjectsAfterDeleteDataForRemovedType {
- [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- }];
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.objectClasses = @[StringObject.class];
- config.fileURL = RLMTestRealmURL();
- config.schemaVersion = 1;
- config.migrationBlock = ^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNotNil(oldObject);
- XCTAssertNil(newObject);
- }];
- [migration deleteDataForClassName:IntObject.className];
- [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *) {
- XCTFail(@"should not have enumerated any objects");
- }];
- [migration deleteDataForClassName:IntObject.className];
- };
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- }
- - (void)testEnumerateObjectsAfterDeleteData {
- [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- }];
- [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:IntObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNotNil(oldObject);
- XCTAssertNotNil(newObject);
- }];
- [migration deleteDataForClassName:IntObject.className];
- [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *) {
- XCTFail(@"should not have enumerated any objects");
- }];
- }];
- }
- - (RLMResults *)objectsOfType:(Class)cls {
- auto config = self.config;
- config.schemaVersion = 1;
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- return [cls allObjectsInRealm:realm];
- }
- - (void)testDeleteSomeObjectsWithinMigration {
- [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- [IntObject createInRealm:realm withValue:@[@3]];
- }];
- [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
- if ([newObject[@"intCol"] intValue] != 2) {
- [migration deleteObject:newObject];
- }
- }];
- }];
- XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2]));
- }
- - (void)testDeleteObjectsWithinSeparateEnumerations {
- [self createTestRealmWithClasses:@[IntObject.class] block:^(RLMRealm *realm) {
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- [IntObject createInRealm:realm withValue:@[@3]];
- }];
- [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
- if ([newObject[@"intCol"] intValue] == 1) {
- [migration deleteObject:newObject];
- }
- }];
- [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
- if ([newObject[@"intCol"] intValue] == 3) {
- [migration deleteObject:newObject];
- }
- }];
- }];
- XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2]));
- }
- - (void)testDeleteAndRecreateObjectsWithinMigration {
- [self createTestRealmWithClasses:@[IntObject.class, PrimaryIntObject.class] block:^(RLMRealm *realm) {
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- [PrimaryIntObject createInRealm:realm withValue:@[@1]];
- [PrimaryIntObject createInRealm:realm withValue:@[@2]];
- }];
- [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:IntObject.className block:^(RLMObject *, RLMObject *newObject) {
- [migration deleteObject:newObject];
- }];
- [migration enumerateObjects:PrimaryIntObject.className block:^(RLMObject *, RLMObject *newObject) {
- [migration deleteObject:newObject];
- }];
- [migration createObject:IntObject.className withValue:@[@2]];
- [migration createObject:IntObject.className withValue:@[@4]];
- [migration createObject:PrimaryIntObject.className withValue:@[@2]];
- [migration createObject:PrimaryIntObject.className withValue:@[@4]];
- }];
- XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
- XCTAssertEqualObjects([[self objectsOfType:PrimaryIntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
- }
- - (void)testDeleteAllDataAndRecreateObjectsWithinMigration {
- [self createTestRealmWithClasses:@[IntObject.class, PrimaryIntObject.class] block:^(RLMRealm *realm) {
- [IntObject createInRealm:realm withValue:@[@1]];
- [IntObject createInRealm:realm withValue:@[@2]];
- [PrimaryIntObject createInRealm:realm withValue:@[@1]];
- [PrimaryIntObject createInRealm:realm withValue:@[@2]];
- }];
- [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration deleteDataForClassName:IntObject.className];
- [migration deleteDataForClassName:PrimaryIntObject.className];
- [migration createObject:IntObject.className withValue:@[@2]];
- [migration createObject:IntObject.className withValue:@[@4]];
- [migration createObject:PrimaryIntObject.className withValue:@[@2]];
- [migration createObject:PrimaryIntObject.className withValue:@[@4]];
- }];
- XCTAssertEqualObjects([[self objectsOfType:IntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
- XCTAssertEqualObjects([[self objectsOfType:PrimaryIntObject.class] valueForKey:@"intCol"], (@[@2, @4]));
- }
- - (void)testRequiredToNullableAutoMigration {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllOptionalTypes.class];
- [objectSchema.properties setValue:@NO forKey:@"optional"];
- // create initial required column
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [AllOptionalTypes createInRealm:realm withValue:@[@1, @1, @1, @1, @"str",
- [@"data" dataUsingEncoding:NSUTF8StringEncoding],
- [NSDate dateWithTimeIntervalSince1970:1]]];
- [AllOptionalTypes createInRealm:realm withValue:@[@2, @2, @2, @0, @"str2",
- [@"data2" dataUsingEncoding:NSUTF8StringEncoding],
- [NSDate dateWithTimeIntervalSince1970:2]]];
- }];
- RLMRealm *realm = [self migrateTestRealmWithBlock:nil];
- RLMResults *allObjects = [AllOptionalTypes allObjectsInRealm:realm];
- XCTAssertEqual(2U, allObjects.count);
- AllOptionalTypes *obj = allObjects[0];
- XCTAssertEqualObjects(@1, obj.intObj);
- XCTAssertEqualObjects(@1, obj.floatObj);
- XCTAssertEqualObjects(@1, obj.doubleObj);
- XCTAssertEqualObjects(@1, obj.boolObj);
- XCTAssertEqualObjects(@"str", obj.string);
- XCTAssertEqualObjects([@"data" dataUsingEncoding:NSUTF8StringEncoding], obj.data);
- XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:1], obj.date);
- obj = allObjects[1];
- XCTAssertEqualObjects(@2, obj.intObj);
- XCTAssertEqualObjects(@2, obj.floatObj);
- XCTAssertEqualObjects(@2, obj.doubleObj);
- XCTAssertEqualObjects(@0, obj.boolObj);
- XCTAssertEqualObjects(@"str2", obj.string);
- XCTAssertEqualObjects([@"data2" dataUsingEncoding:NSUTF8StringEncoding], obj.data);
- XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:2], obj.date);
- }
- - (void)testNullableToRequiredMigration {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllOptionalTypes.class];
- // create initial nullable column
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- [AllOptionalTypes createInRealm:realm withValue:@[ [NSNull null], [NSNull null], [NSNull null], [NSNull null],
- [NSNull null], [NSNull null], [NSNull null]]];
- [AllOptionalTypes createInRealm:realm withValue:@[@2, @2, @2, @0, @"str2",
- [@"data2" dataUsingEncoding:NSUTF8StringEncoding],
- [NSDate dateWithTimeIntervalSince1970:2]]];
- }];
- objectSchema.objectClass = RLMObject.class;
- [objectSchema.properties setValue:@NO forKey:@"optional"];
- RLMRealm *realm;
- @autoreleasepool {
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.fileURL = RLMTestRealmURL();
- config.customSchema = [self schemaWithObjects:@[ objectSchema ]];
- config.schemaVersion = 1;
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMAssertRealmSchemaMatchesTable(self, realm);
- }
- RLMResults *allObjects = [AllOptionalTypes allObjectsInRealm:realm];
- XCTAssertEqual(2U, allObjects.count);
- AllOptionalTypes *obj = allObjects[0];
- XCTAssertEqualObjects(@0, obj[@"intObj"]);
- XCTAssertEqualObjects(@0, obj[@"floatObj"]);
- XCTAssertEqualObjects(@0, obj[@"doubleObj"]);
- XCTAssertEqualObjects(@0, obj[@"boolObj"]);
- XCTAssertEqualObjects(@"", obj[@"string"]);
- XCTAssertEqualObjects(NSData.data, obj[@"data"]);
- XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:0], obj[@"date"]);
- obj = allObjects[1];
- XCTAssertEqualObjects(@0, obj[@"intObj"]);
- XCTAssertEqualObjects(@0, obj[@"floatObj"]);
- XCTAssertEqualObjects(@0, obj[@"doubleObj"]);
- XCTAssertEqualObjects(@0, obj[@"boolObj"]);
- XCTAssertEqualObjects(@"", obj[@"string"]);
- XCTAssertEqualObjects(NSData.data, obj[@"data"]);
- XCTAssertEqualObjects([NSDate dateWithTimeIntervalSince1970:0], obj[@"date"]);
- }
- - (void)testMigrationAfterReorderingProperties {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:RequiredPropertiesObject.class];
- // Create a table where the order of columns does not match the order the properties are declared in the class.
- objectSchema.properties = @[ objectSchema.properties[2], objectSchema.properties[0], objectSchema.properties[1] ];
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- // We use a dictionary here to ensure that the test reaches the migration case below, even if the non-migration
- // case doesn't handle the ordering correctly. The non-migration case is tested in testRearrangeProperties.
- [RequiredPropertiesObject createInRealm:realm withValue:@{ @"stringCol": @"Hello", @"dateCol": [NSDate date], @"binaryCol": [NSData data] }];
- }];
- objectSchema = [RLMObjectSchema schemaForObjectClass:RequiredPropertiesObject.class];
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.fileURL = RLMTestRealmURL();
- config.customSchema = [self schemaWithObjects:@[objectSchema]];
- config.schemaVersion = 1;
- config.migrationBlock = ^(RLMMigration *migration, uint64_t) {
- [migration createObject:RequiredPropertiesObject.className withValue:@[@"World", [NSData data], [NSDate date]]];
- };
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMResults *allObjects = [RequiredPropertiesObject allObjectsInRealm:realm];
- XCTAssertEqualObjects(@"Hello", [allObjects[0] stringCol]);
- XCTAssertEqualObjects(@"World", [allObjects[1] stringCol]);
- }
- #ifndef REALM_SPM
- - (void)testDateTimeFormatAutoMigration {
- static const int cookieValue = 0xDEADBEEF;
- NSDate *distantPast = NSDate.distantPast;
- NSDate *distantFuture = NSDate.distantFuture;
- NSDate *beforeEpoch = [NSDate dateWithTimeIntervalSince1970:-100];
- NSDate *epoch = [NSDate dateWithTimeIntervalSince1970:0];
- NSDate *afterEpoch = [NSDate dateWithTimeIntervalSince1970:100];
- NSDate *referenceDate = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
- NSArray *expectedDates = @[distantPast, distantFuture, beforeEpoch, epoch, afterEpoch, referenceDate];
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.objectClasses = @[[DateMigrationObject class]];
- @autoreleasepool {
- #if RLM_OLD_DATE_FORMAT
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- [realm beginWriteTransaction];
- for (NSDate *date in expectedDates) {
- [DateMigrationObject createInRealm:realm withValue:@[date, date, date, date, @(cookieValue)]];
- [DateMigrationObject createInRealm:realm withValue:@[date, NSNull.null, date, NSNull.null, @(cookieValue)]];
- }
- [realm commitWriteTransaction];
- NSURL *url = [config.fileURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:@"fileformat-old-date.realm"];
- [realm writeCopyToURL:url encryptionKey:nil error:nil];
- NSLog(@"wrote pre-migration realm to %@", url);
- #else
- NSURL *bundledRealmURL = [[NSBundle bundleForClass:[DateMigrationObject class]]
- URLForResource:@"fileformat-old-date" withExtension:@"realm"];
- [NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:nil];
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMResults *dates = [DateMigrationObject allObjectsInRealm:realm];
- XCTAssertEqual(expectedDates.count * 2, dates.count);
- for (NSUInteger i = 0; i < expectedDates.count; ++i) {
- NSDate *expected = expectedDates[i];
- DateMigrationObject *obj = dates[i * 2];
- XCTAssertEqualObjects(obj.nonNullNonIndexed, expected);
- XCTAssertEqualObjects(obj.nonNullIndexed, expected);
- XCTAssertEqualObjects(obj.nullNonIndexed, expected);
- XCTAssertEqualObjects(obj.nullIndexed, expected);
- XCTAssertEqual(obj.cookie, cookieValue);
- obj = dates[i * 2 + 1];
- XCTAssertEqualObjects(obj.nonNullNonIndexed, expected);
- XCTAssertEqualObjects(obj.nonNullIndexed, expected);
- XCTAssertNil(obj.nullNonIndexed);
- XCTAssertNil(obj.nullIndexed);
- XCTAssertEqual(obj.cookie, cookieValue);
- }
- for (NSDate *date in expectedDates) {
- RLMResults *results = [DateMigrationObject objectsInRealm:realm
- where:@"nonNullIndexed = %@ AND nullIndexed = %@",
- date, date];
- XCTAssertEqual(1U, results.count);
- DateMigrationObject *obj = results.firstObject;
- XCTAssertEqualObjects(date, obj.nonNullIndexed);
- XCTAssertEqualObjects(date, obj.nullIndexed);
- results = [DateMigrationObject objectsInRealm:realm
- where:@"nonNullIndexed = %@ AND nullIndexed = nil", date];
- XCTAssertEqual(1U, results.count);
- obj = results.firstObject;
- XCTAssertEqualObjects(date, obj.nonNullIndexed);
- XCTAssertNil(obj.nullIndexed);
- }
- #endif
- }
- @autoreleasepool {
- NSURL *bundledRealmURL = [[NSBundle bundleForClass:[DateMigrationObject class]]
- URLForResource:@"fileformat-pre-null" withExtension:@"realm"];
- NSError *error;
- [NSFileManager.defaultManager removeItemAtURL:config.fileURL error:&error];
- XCTAssertNil(error);
- [NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:&error];
- XCTAssertNil(error);
- config.schemaVersion = 1; // Nullability of some properties changed
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMResults *dates = [DateMigrationObject allObjectsInRealm:realm];
- XCTAssertEqual(expectedDates.count, dates.count);
- for (NSUInteger i = 0; i < expectedDates.count; ++i) {
- NSDate *expected = expectedDates[i];
- DateMigrationObject *obj = dates[i];
- XCTAssertEqualObjects(obj.nonNullNonIndexed, expected);
- XCTAssertEqualObjects(obj.nonNullIndexed, expected);
- XCTAssertEqualObjects(obj.nullNonIndexed, expected);
- XCTAssertEqualObjects(obj.nullIndexed, expected);
- XCTAssertEqual(obj.cookie, cookieValue);
- }
- }
- }
- - (void)testMigratingFromMixed {
- NSArray *values = @[@YES, @1, @1.1, @1.2f, @"str",
- [@"data" dataUsingEncoding:NSUTF8StringEncoding],
- [NSDate dateWithTimeIntervalSince1970:100]];
- RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
- config.objectClasses = @[[AllTypesObject class], [LinkToAllTypesObject class], [StringObject class]];
- #if 0 // Code for generating the test realm
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- [realm beginWriteTransaction];
- for (id value in values) {
- [AllTypesObject createInRealm:realm withValue:@[@NO, @0, @0, @0, @"",
- NSData.data, NSDate.date,
- @NO, @0, value, NSNull.null]];
- }
- [realm commitWriteTransaction];
- NSURL *url = [config.fileURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:@"mixed-column.realm"];
- [realm writeCopyToURL:url encryptionKey:nil error:nil];
- NSLog(@"wrote pre-migration realm to %@", url);
- #else
- NSURL *bundledRealmURL = [[NSBundle bundleForClass:[DateMigrationObject class]]
- URLForResource:@"mixed-column" withExtension:@"realm"];
- [NSFileManager.defaultManager removeItemAtURL:config.fileURL error:nil];
- [NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:nil];
- __block bool migrationCalled = false;
- config.schemaVersion = 1;
- config.migrationBlock = ^(RLMMigration *migration, uint64_t) {
- __block NSUInteger i = values.count;
- [migration enumerateObjects:@"AllTypesObject" block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(values[--i], oldObject[@"mixedCol"]);
- RLMAssertThrowsWithReasonMatching(newObject[@"mixedCol"],
- @"Invalid property name 'mixedCol' for class 'AllTypesObject'.");
- }];
- migrationCalled = true;
- };
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- XCTAssertTrue(migrationCalled);
- #endif
- }
- #endif // REALM_SPM
- - (void)testModifyPrimaryKeyInMigration {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:PrimaryStringObject.class];
- objectSchema.primaryKeyProperty = objectSchema[@"intCol"];
- [self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
- for (int i = 0; i < 10; ++i) {
- [PrimaryStringObject createInRealm:realm withValue:@[@(i).stringValue, @(i + 10)]];
- }
- }];
- auto realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
- [migration enumerateObjects:@"PrimaryStringObject" block:^(RLMObject *oldObject, RLMObject *newObject) {
- newObject[@"stringCol"] = [oldObject[@"intCol"] stringValue];
- }];
- }];
- for (int i = 10; i < 20; ++i) {
- auto obj = [PrimaryStringObject objectInRealm:realm forPrimaryKey:@(i).stringValue];
- XCTAssertNotNil(obj);
- XCTAssertEqual(obj.intCol, i);
- }
- }
- #pragma mark - Property Rename
- // Successful Property Rename Tests
- - (void)testMigrationRenameProperty {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:AllTypesObject.class];
- RLMObjectSchema *stringObjectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
- RLMObjectSchema *linkingObjectsSchema = [RLMObjectSchema schemaForObjectClass:LinkToAllTypesObject.class];
- NSMutableArray *beforeProperties = [NSMutableArray arrayWithCapacity:objectSchema.properties.count];
- for (RLMProperty *property in objectSchema.properties) {
- [beforeProperties addObject:[property copyWithNewName:[NSString stringWithFormat:@"before_%@", property.name]]];
- }
- NSArray *afterProperties = objectSchema.properties;
- objectSchema.properties = beforeProperties;
- NSDate *now = [NSDate dateWithTimeIntervalSince1970:100000];
- id inputValue = @[@YES, @1, @1.1f, @1.11, @"string", [NSData dataWithBytes:"a" length:1], now, @YES, @11, @[@"a"]];
- [self createTestRealmWithSchema:@[objectSchema, stringObjectSchema, linkingObjectsSchema] block:^(RLMRealm *realm) {
- [AllTypesObject createInRealm:realm withValue:inputValue];
- }];
- objectSchema.properties = afterProperties;
- RLMRealmConfiguration *config = [self renameConfigurationWithObjectSchemas:@[objectSchema, stringObjectSchema, linkingObjectsSchema]
- migrationBlock:^(RLMMigration *migration, __unused uint64_t oldSchemaVersion) {
- [afterProperties enumerateObjectsUsingBlock:^(RLMProperty *property, NSUInteger idx, __unused BOOL *stop) {
- [migration renamePropertyForClass:AllTypesObject.className oldName:[beforeProperties[idx] name] newName:property.name];
- [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertNotNil(oldObject[[beforeProperties[idx] name]]);
- RLMAssertThrowsWithReasonMatching(newObject[[beforeProperties[idx] name]], @"Invalid property name");
- if (![property.objectClassName isEqualToString:@""]) { return; }
- XCTAssertEqualObjects(oldObject[[beforeProperties[idx] name]], newObject[property.name]);
- }];
- }];
- [migration enumerateObjects:AllTypesObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects([oldObject.description stringByReplacingOccurrencesOfString:@"before_" withString:@""], newObject.description);
- }];
- }];
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
- RLMAssertRealmSchemaMatchesTable(self, realm);
- RLMResults *allObjects = [AllTypesObject allObjectsInRealm:realm];
- XCTAssertEqual(1U, allObjects.count);
- XCTAssertEqual(1U, [[StringObject allObjectsInRealm:realm] count]);
- AllTypesObject *obj = allObjects.firstObject;
- XCTAssertEqualObjects(inputValue[0], @(obj.boolCol));
- XCTAssertEqualObjects(inputValue[1], @(obj.intCol));
- XCTAssertEqualObjects(inputValue[2], @(obj.floatCol));
- XCTAssertEqualObjects(inputValue[3], @(obj.doubleCol));
- XCTAssertEqualObjects(inputValue[4], obj.stringCol);
- XCTAssertEqualObjects(inputValue[5], obj.binaryCol);
- XCTAssertEqualObjects(inputValue[6], obj.dateCol);
- XCTAssertEqualObjects(inputValue[7], @(obj.cBoolCol));
- XCTAssertEqualObjects(inputValue[8], @(obj.longCol));
- XCTAssertEqualObjects(inputValue[9], @[obj.objectCol.stringCol]);
- }
- - (void)testMultipleMigrationRenameProperty {
- RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
- schema.properties = @[[schema.properties.firstObject copyWithNewName:@"stringCol0"]];
- [self createTestRealmWithSchema:@[schema] block:^(RLMRealm *realm) {
- [StringObject createInRealm:realm withValue:@[@"0"]];
- }];
- schema.properties = @[[schema.properties.firstObject copyWithNewName:@"stringCol"]];
- __block bool migrationCalled = false;
- RLMRealmConfiguration *config = [RLMRealmConfiguration new];
- config.fileURL = RLMTestRealmURL();
- config.customSchema = [self schemaWithObjects:@[schema]];
- config.schemaVersion = 2;
- config.migrationBlock = ^(RLMMigration *migration, uint64_t oldVersion){
- migrationCalled = true;
- __block id oldValue = nil;
- if (oldVersion < 1) {
- [migration renamePropertyForClass:StringObject.className oldName:@"stringCol0" newName:@"stringCol1"];
- [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- oldValue = oldObject[@"stringCol0"];
- XCTAssertNotNil(oldValue);
- RLMAssertThrowsWithReasonMatching(newObject[@"stringCol0"], @"Invalid property name");
- RLMAssertThrowsWithReasonMatching(newObject[@"stringCol1"], @"Invalid property name");
- }];
- }
- if (oldVersion < 2) {
- [migration renamePropertyForClass:StringObject.className oldName:@"stringCol1" newName:@"stringCol"];
- [migration enumerateObjects:StringObject.className block:^(RLMObject *oldObject, RLMObject *newObject) {
- XCTAssertEqualObjects(oldObject[@"stringCol0"], oldValue);
- XCTAssertEqualObjects(newObject[@"stringCol"], oldValue);
- RLMAssertThrowsWithReasonMatching(newObject[@"stringCol0"], @"Invalid property name");
- RLMAssertThrowsWithReasonMatching(newObject[@"stringCol1"], @"Invalid property name");
- }];
- }
- };
- XCTAssertTrue([RLMRealm performMigrationForConfiguration:config error:nil]);
- XCTAssertTrue(migrationCalled);
- XCTAssertEqualObjects(@"0", [[[StringObject allObjectsInRealm:[RLMRealm realmWithConfiguration:config error:nil]] firstObject] stringCol]);
- }
- - (void)testMigrationRenamePropertyPrimaryKeyBoth {
- [self assertPropertyRenameError:nil firstSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
- schema.primaryKeyProperty = beforeProperty;
- } secondSchemaTransform:^(RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) {
- schema.primaryKeyProperty = afterProperty;
- }];
- }
- - (void)testMigrationRenamePropertyUnsetPrimaryKey {
- [self assertPropertyRenameError:nil firstSchemaTransform:^(RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
- schema.primaryKeyProperty = beforeProperty;
- } secondSchemaTransform:^(RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
- schema.primaryKeyProperty = nil;
- }];
- }
- - (void)testMigrationRenamePropertySetPrimaryKey {
- [self assertPropertyRenameError:nil firstSchemaTransform:nil
- secondSchemaTransform:^(RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) {
- schema.primaryKeyProperty = afterProperty;
- }];
- }
- - (void)testMigrationRenamePropertyIndexBoth {
- [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
- afterProperty.indexed = YES;
- beforeProperty.indexed = YES;
- } secondSchemaTransform:nil];
- }
- - (void)testMigrationRenamePropertyUnsetIndex {
- [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
- beforeProperty.indexed = YES;
- } secondSchemaTransform:nil];
- }
- - (void)testMigrationRenamePropertySetIndex {
- [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) {
- afterProperty.indexed = YES;
- } secondSchemaTransform:nil];
- }
- - (void)testMigrationRenamePropertySetOptional {
- [self assertPropertyRenameError:nil firstSchemaTransform:^(__unused RLMObjectSchema *schema, RLMProperty *beforeProperty, __unused RLMProperty *afterProperty) {
- beforeProperty.optional = NO;
- } secondSchemaTransform:nil];
- }
- // Unsuccessful Property Rename Tests
- - (void)testMigrationRenamePropertySetRequired {
- [self assertPropertyRenameError:@"Cannot rename property 'StringObject.before_stringCol' to 'stringCol' because it would change from optional to required."
- firstSchemaTransform:^(__unused RLMObjectSchema *schema, __unused RLMProperty *beforeProperty, RLMProperty *afterProperty) {
- afterProperty.optional = NO;
- } secondSchemaTransform:nil];
- }
- - (void)testMigrationRenamePropertyTypeMismatch {
- [self assertPropertyRenameError:@"Cannot rename property 'StringObject.before_stringCol' to 'stringCol' because it would change from type 'int' to 'string'."
- firstSchemaTransform:^(RLMObjectSchema *, RLMProperty *beforeProperty, RLMProperty *) {
- beforeProperty.type = RLMPropertyTypeInt;
- } secondSchemaTransform:nil];
- }
- - (void)testMigrationRenamePropertyObjectTypeMismatch {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationLinkObject.class];
- RLMObjectSchema *migrationObjectSchema = [RLMObjectSchema schemaForObjectClass:MigrationTestObject.class];
- NSArray *afterProperties = objectSchema.properties;
- NSMutableArray *beforeProperties = [NSMutableArray arrayWithCapacity:2];
- for (RLMProperty *property in afterProperties) {
- RLMProperty *beforeProperty = [property copyWithNewName:[NSString stringWithFormat:@"before_%@", property.name]];
- beforeProperty.objectClassName = MigrationLinkObject.className;
- [beforeProperties addObject:beforeProperty];
- }
- objectSchema.properties = beforeProperties;
- [self createTestRealmWithSchema:@[objectSchema] block:^(__unused RLMRealm *realm) {
- // No need to create an object
- }];
- objectSchema.properties = afterProperties;
- [self assertPropertyRenameError:@"Cannot rename property 'MigrationLinkObject.before_object' to 'object' because it would change from type '<MigrationLinkObject>' to '<MigrationTestObject>'."
- objectSchemas:@[objectSchema, migrationObjectSchema]
- className:MigrationLinkObject.className
- oldName:[beforeProperties[0] name]
- newName:[afterProperties[0] name]];
- [self assertPropertyRenameError:@"Cannot rename property 'MigrationLinkObject.before_array' to 'array' because it would change from type 'array<MigrationLinkObject>' to 'array<MigrationTestObject>'."
- objectSchemas:@[objectSchema, migrationObjectSchema]
- className:MigrationLinkObject.className
- oldName:[beforeProperties[1] name]
- newName:[afterProperties[1] name]];
- }
- - (void)testMigrationRenameMissingPropertiesAndClasses {
- RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:StringObject.class];
- [self createTestRealmWithSchema:@[objectSchema] block:^(__unused RLMRealm *realm) {
- // No need to create an object
- }];
- // Missing Old Property
- [self assertPropertyRenameError:@"Cannot rename property 'StringObject.nonExistentProperty1' because it does not exist."
- objectSchemas:@[objectSchema] className:StringObject.className
- oldName:@"nonExistentProperty1" newName:@"nonExistentProperty2"];
- // Missing New Property
- RLMObjectSchema *renamedProperty = [objectSchema copy];
- renamedProperty.properties[0].name = @"stringCol2";
- [self assertPropertyRenameError:@"Renamed property 'StringObject.nonExistentProperty' does not exist."
- objectSchemas:@[renamedProperty] className:StringObject.className
- oldName:@"stringCol" newName:@"nonExistentProperty"];
- // Removed Class
- [self assertPropertyRenameError:@"Cannot rename properties for type 'StringObject' because it has been removed from the Realm."
- objectSchemas:@[[RLMObjectSchema schemaForObjectClass:IntObject.class]]
- className:StringObject.className oldName:@"stringCol" newName:@"stringCol2"];
- // Without Removing Old Property
- RLMProperty *secondProperty = [objectSchema.properties.firstObject copyWithNewName:@"stringCol2"];
- objectSchema.properties = [objectSchema.properties arrayByAddingObject:secondProperty];
- [self assertPropertyRenameError:@"Cannot rename property 'StringObject.stringCol' to 'stringCol2' because the source property still exists."
- objectSchemas:@[objectSchema] className:StringObject.className oldName:@"stringCol" newName:@"stringCol2"];
- }
- @end
|