From 7959aec3857fa9791a35f51b60bb76fcacb9104b Mon Sep 17 00:00:00 2001 From: "Samuel E. Giddins" Date: Wed, 24 Jun 2015 13:13:41 -0700 Subject: [PATCH 1/2] [MigrationTests] Add testMigrationProperlySetsPropertyColumns --- Realm/Tests/MigrationTests.mm | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/Realm/Tests/MigrationTests.mm b/Realm/Tests/MigrationTests.mm index a24437d407..3e622cf40e 100644 --- a/Realm/Tests/MigrationTests.mm +++ b/Realm/Tests/MigrationTests.mm @@ -31,6 +31,16 @@ #import "object_store.hpp" #import +static void RLMAssertRealmSchemaMatchesTable(id self, RLMRealm *realm) { + for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) { + Table *table = objectSchema.table; + for (RLMProperty *property in objectSchema.properties) { + XCTAssertEqual(property.column, table->get_column_index(RLMStringDataWithNSString(property.name))); + XCTAssertEqual(property.indexed, table->has_search_index(property.column)); + } + } +} + @interface MigrationObject : RLMObject @property int intCol; @property NSString *stringCol; @@ -143,6 +153,9 @@ - (void)testPerRealmMigration { }]; RLMRealm *anotherRealm = [RLMRealm realmWithPath:RLMTestRealmPath()]; + RLMAssertRealmSchemaMatchesTable(self, [RLMRealm defaultRealm]); + RLMAssertRealmSchemaMatchesTable(self, anotherRealm); + XCTAssertEqual(2U, [RLMRealm schemaVersionAtPath:anotherRealm.path encryptionKey:nil error:nil]); XCTAssertTrue(migrationComplete); } @@ -175,6 +188,7 @@ - (void)testRemovingSubclass { @autoreleasepool { // verify migration RLMRealm *realm = [self realmWithTestPath]; + RLMAssertRealmSchemaMatchesTable(self, realm); XCTAssertFalse(ObjectStore::table_for_object_type(realm.group, "DeletedClass"), @"The deleted class should not have a table."); XCTAssertEqual(0U, [StringObject allObjectsInRealm:realm].count); } @@ -218,6 +232,7 @@ - (void)testAddingPropertyAtEnd { // verify migration @autoreleasepool { RLMRealm *realm = [self realmWithTestPath]; + RLMAssertRealmSchemaMatchesTable(self, realm); 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"); @@ -250,6 +265,7 @@ - (void)testAddingPropertyAtBeginningPreservesData { // verify migration realm = [self realmWithTestPath]; + RLMAssertRealmSchemaMatchesTable(self, realm); ThreeFieldMigrationObject *mig = [ThreeFieldMigrationObject allObjectsInRealm:realm][0]; XCTAssertEqual(0, mig.col1); XCTAssertEqual(1, mig.col2); @@ -286,6 +302,7 @@ - (void)testRemoveProperty { // verify migration realm = [self realmWithTestPath]; + RLMAssertRealmSchemaMatchesTable(self, realm); MigrationObject *mig1 = [MigrationObject allObjectsInRealm:realm][1]; XCTAssertThrows(mig1[@"deletedCol"], @"Deleted column should no longer be accessible."); } @@ -322,10 +339,39 @@ - (void)testRemoveAndAddProperty { // verify migration realm = [self realmWithTestPath]; + RLMAssertRealmSchemaMatchesTable(self, realm); MigrationObject *mig1 = [MigrationObject allObjectsInRealm:realm][1]; XCTAssertThrows(mig1[@"oldIntCol"], @"Deleted column should no longer be accessible."); } +- (void)testMigrationProperlySetsPropertyColumns { + RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationObject.class]; + objectSchema.properties = @[ + [[RLMProperty alloc] initWithName:@"firstName" type:RLMPropertyTypeString objectClassName:nil indexed:false optional:NO], + [[RLMProperty alloc] initWithName:@"lastName" type:RLMPropertyTypeString objectClassName:nil indexed:false optional:NO], + [[RLMProperty alloc] initWithName:@"age" type:RLMPropertyTypeInt objectClassName:nil indexed:false optional:NO], + ]; + + RLMRealm *realm = [self realmWithSingleObject:objectSchema]; + [realm beginWriteTransaction]; + [realm createObject:MigrationObject.className withValue:@[@"a", @"b", @1]]; + [realm createObject:MigrationObject.className withValue:@[@"c", @"d", @2]]; + [realm commitWriteTransaction]; + + [RLMRealm setSchemaVersion:1 forRealmAtPath:RLMTestRealmPath() withMigrationBlock:^(__unused RLMMigration *migration, uint64_t oldSchemaVersion) { + XCTAssertEqual(oldSchemaVersion, 0U, @"Initial schema version should be 0"); + }]; + + // verify migration + objectSchema.properties = @[ + [[RLMProperty alloc] initWithName:@"fullName" type:RLMPropertyTypeString objectClassName:nil indexed:false optional:NO], + [[RLMProperty alloc] initWithName:@"age" type:RLMPropertyTypeInt objectClassName:nil indexed:false optional:NO], + [[RLMProperty alloc] initWithName:@"pets" type:RLMPropertyTypeArray objectClassName:MigrationObject.className indexed:false optional:NO], + ]; + realm = [self realmWithSingleObject:objectSchema]; + RLMAssertRealmSchemaMatchesTable(self, realm); +} + - (void)testChangePropertyType { // make string an int RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:MigrationObject.class]; @@ -356,6 +402,7 @@ - (void)testChangePropertyType { // verify migration realm = [self realmWithTestPath]; + RLMAssertRealmSchemaMatchesTable(self, realm); MigrationObject *mig1 = [MigrationObject allObjectsInRealm:realm][1]; XCTAssertEqualObjects(mig1[@"stringCol"], @"2", @"stringCol should be string after migration."); } @@ -389,6 +436,7 @@ - (void)testPrimaryKeyMigration { }]; }]; [RLMRealm migrateRealmAtPath:RLMTestRealmPath()]; + RLMAssertRealmSchemaMatchesTable(self, [self realmWithTestPath]); } - (void)testRemovePrimaryKeyMigration { @@ -417,6 +465,7 @@ - (void)testRemovePrimaryKeyMigration { }]; XCTAssertNoThrow([self realmWithSingleObject:objectSchema]); + RLMAssertRealmSchemaMatchesTable(self, [self realmWithSingleObject:objectSchema]); } - (void)testStringPrimaryKeyMigration { @@ -441,6 +490,7 @@ - (void)testStringPrimaryKeyMigration { }]; }]; [RLMRealm migrateRealmAtPath:RLMTestRealmPath()]; + RLMAssertRealmSchemaMatchesTable(self, [self realmWithTestPath]); } - (void)testStringPrimaryKeyNoIndexMigration { @@ -466,6 +516,7 @@ - (void)testStringPrimaryKeyNoIndexMigration { }]; }]; [RLMRealm migrateRealmAtPath:RLMTestRealmPath()]; + RLMAssertRealmSchemaMatchesTable(self, [self realmWithTestPath]); } - (void)testIntPrimaryKeyNoIndexMigration { @@ -490,6 +541,7 @@ - (void)testIntPrimaryKeyNoIndexMigration { // check that column is now indexed RLMRealm *realm = [self realmWithTestPath]; + RLMAssertRealmSchemaMatchesTable(self, realm); XCTAssertTrue(realm.schema[MigrationPrimaryKeyObject.className].table->has_search_index(0)); // verify that old data still exists @@ -535,6 +587,7 @@ - (void)testDuplicatePrimaryKeyMigration { XCTAssertEqual(true, duplicateDeleted); }]; [RLMRealm migrateRealmAtPath:RLMTestRealmPath()]; + RLMAssertRealmSchemaMatchesTable(self, [self realmWithTestPath]); // make sure deletion occurred XCTAssertEqual(1U, [[MigrationPrimaryKeyObject allObjectsInRealm:[RLMRealm realmWithPath:RLMTestRealmPath()]] count]); @@ -681,6 +734,8 @@ - (void)testRearrangeProperties { // test object from other realm still works XCTAssertEqualObjects(obj.data, @"new data"); + RLMAssertRealmSchemaMatchesTable(self, realm); + // verify schema for both objects NSArray *properties = defaultObj.objectSchema.properties; for (NSUInteger i = 0; i < properties.count; i++) { From fd69a4030f8b3afaeb8840ac537b4cf953e61413 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 30 Jun 2015 11:09:35 -0700 Subject: [PATCH 2/2] Eliminate a redundant call to RLMRealmSetSchemaAndAlign when migrations occur It's not idempotent, so the extra call actively broke things when migrations ran. --- Realm/RLMObjectStore.mm | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Realm/RLMObjectStore.mm b/Realm/RLMObjectStore.mm index 2256501a96..9362a88f57 100644 --- a/Realm/RLMObjectStore.mm +++ b/Realm/RLMObjectStore.mm @@ -71,9 +71,14 @@ void RLMClearAccessorCache() { } static void RLMCopyColumnMapping(RLMObjectSchema *targetSchema, const ObjectSchema &tableSchema) { + REALM_ASSERT_DEBUG(targetSchema.properties.count == tableSchema.properties.size()); + // copy updated column mapping - for (size_t i = 0; i < tableSchema.properties.size(); i++) { - ((RLMProperty *)targetSchema.properties[i]).column = tableSchema.properties[i].table_column; + size_t i = 0; + for (RLMProperty *targetProp in targetSchema.properties) { + REALM_ASSERT_DEBUG(targetProp.name.UTF8String == tableSchema.properties[i].name); + targetProp.column = tableSchema.properties[i].table_column; + ++i; } // re-order properties @@ -149,7 +154,8 @@ void RLMUpdateRealmToSchemaVersion(RLMRealm *realm, NSUInteger newVersion, RLMSc // write transaction [realm beginWriteTransaction]; - bool changed = ObjectStore::update_realm_with_schema(realm.group, newVersion, schema, [=](__unused Group *group, ObjectStore::Schema &schema) { + bool migrationCalled = false; + bool changed = ObjectStore::update_realm_with_schema(realm.group, newVersion, schema, [&](__unused Group *group, ObjectStore::Schema &schema) { RLMRealmSetSchemaAndAlign(realm, targetSchema, schema); if (migrationBlock) { NSError *error = migrationBlock(); @@ -158,8 +164,12 @@ void RLMUpdateRealmToSchemaVersion(RLMRealm *realm, NSUInteger newVersion, RLMSc @throw RLMException(error.description); } } + migrationCalled = true; }); - RLMRealmSetSchemaAndAlign(realm, targetSchema, schema); + + if (!migrationCalled) { + RLMRealmSetSchemaAndAlign(realm, targetSchema, schema); + } if (changed) { [realm commitWriteTransaction];