You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1767 lines
83 KiB
1767 lines
83 KiB
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 MigrationObject : RLMObject
|
|
@property int intCol;
|
|
@property NSString *stringCol;
|
|
@end
|
|
RLM_ARRAY_TYPE(MigrationObject);
|
|
|
|
@implementation MigrationObject
|
|
@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 ThreeFieldMigrationObject : RLMObject
|
|
@property int col1;
|
|
@property int col2;
|
|
@property int col3;
|
|
@end
|
|
|
|
@implementation ThreeFieldMigrationObject
|
|
@end
|
|
|
|
@interface MigrationTwoStringObject : RLMObject
|
|
@property NSString *col1;
|
|
@property NSString *col2;
|
|
@end
|
|
|
|
@implementation MigrationTwoStringObject
|
|
@end
|
|
|
|
@interface MigrationLinkObject : RLMObject
|
|
@property MigrationObject *object;
|
|
@property RLMArray<MigrationObject> *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 = @[MigrationObject.class];
|
|
@autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
|
|
|
|
config.objectClasses = @[MigrationObject.class, ThreeFieldMigrationObject.class];
|
|
XCTAssertNoThrow([RLMRealm realmWithConfiguration:config error:nil]);
|
|
}
|
|
|
|
- (void)testRemovingClassDoesNotRequireMigration {
|
|
RLMRealmConfiguration *config = [RLMRealmConfiguration new];
|
|
config.objectClasses = @[MigrationObject.class, ThreeFieldMigrationObject.class];
|
|
@autoreleasepool { [RLMRealm realmWithConfiguration:config error:nil]; }
|
|
|
|
config.objectClasses = @[MigrationObject.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:MigrationObject.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:MigrationObject.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:MigrationObject.class];
|
|
|
|
RLMObjectSchema *to = [RLMObjectSchema schemaForObjectClass:MigrationObject.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:MigrationObject.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:[MigrationObject 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:MigrationObject.class];
|
|
objectSchema.properties = @[objectSchema.properties[0]];
|
|
[self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
|
|
[realm createObject:MigrationObject.className withValue:@[@1]];
|
|
[realm createObject:MigrationObject.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:MigrationObject.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
|
|
MigrationObject *mig1 = [MigrationObject 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:ThreeFieldMigrationObject.class];
|
|
objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[2]];
|
|
|
|
[self createTestRealmWithSchema:@[objectSchema] block:^(RLMRealm *realm) {
|
|
[realm createObject:ThreeFieldMigrationObject.className withValue:@[@1, @2]];
|
|
}];
|
|
|
|
RLMRealm *realm = [self migrateTestRealmWithBlock:^(RLMMigration *migration, uint64_t) {
|
|
[migration enumerateObjects:ThreeFieldMigrationObject.className
|
|
block:^(RLMObject *oldObject, RLMObject *newObject) {
|
|
XCTAssertThrows(oldObject[@"col1"]);
|
|
XCTAssertEqualObjects(oldObject[@"col2"], newObject[@"col2"]);
|
|
XCTAssertEqualObjects(oldObject[@"col3"], newObject[@"col3"]);
|
|
}];
|
|
}];
|
|
|
|
// verify migration
|
|
ThreeFieldMigrationObject *mig = [ThreeFieldMigrationObject 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:MigrationObject.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:MigrationObject.className withValue:@[@1, @"1", @YES]];
|
|
[realm createObject:MigrationObject.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:MigrationObject.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
|
|
MigrationObject *mig1 = [MigrationObject 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:MigrationObject.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:MigrationObject.className withValue:@[@1, @"1"]];
|
|
[realm createObject:MigrationObject.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:MigrationObject.className block:migrateObjectBlock];
|
|
}];
|
|
|
|
// verify migration
|
|
MigrationObject *mig1 = [MigrationObject allObjectsInRealm:realm][1];
|
|
XCTAssertThrows(mig1[@"oldIntCol"], @"Deleted column should no longer be accessible.");
|
|
}
|
|
|
|
- (void)testChangePropertyType {
|
|
// make string an int
|
|
RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationObject.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:MigrationObject.className withValue:@[@1, @1]];
|
|
[realm createObject:MigrationObject.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:MigrationObject.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
|
|
MigrationObject *mig1 = [MigrationObject 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:MigrationObject.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:MigrationObject.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:MigrationObject.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:MigrationObject.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]);
|
|
}
|
|
|
|
- (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"];
|
|
[NSFileManager.defaultManager removeItemAtURL:config.fileURL error:nil];
|
|
[NSFileManager.defaultManager copyItemAtURL:bundledRealmURL toURL:config.fileURL error:nil];
|
|
|
|
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
|
|
}
|
|
|
|
- (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:MigrationObject.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 '<MigrationObject>'."
|
|
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<MigrationObject>'."
|
|
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
|
|
|