Realm-cocoa: Attempting to delete a model's data in a migration when another linking model has also been removed crashes

Created on 1 Jun 2016  路  10Comments  路  Source: realm/realm-cocoa

Goals

default.realm.zip

Refactored code that resulted in a few Object subclasses being made redundant. These were removed from the code and a migration was performed that calls Migration.deleteData on these.

Expected Results

Existing data and columns are removed from the Realm file.

Actual Results

Migration crashes with the following error message:

fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=1 "Table is target of cross-table link columns" UserInfo={
    "Error Code" = 1;
    NSLocalizedDescription = "Table is target of cross-table link columns";
}: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-703.0.18.8/src/swift/stdlib/public/core/ErrorType.swift, line 54

Steps to Reproduce

Open the attached default.realm file and try to call migration.deleteData("ReceiptRowSection") during a migration.

Version of Realm and Tooling

Realm version: 1.0
Xcode version: 7.3.1

O-Community P-2-Expected T-Bug-Crash

Most helpful comment

In case this is useful for anyone:
I had two classes:

class Foo: RealmObject(){
    val bar: Bar
    ...
}

and

class Bar: RealmObject {
    ...
}

I didn't need to store those in realm anymore, so in my Migration class I wrote the following:

    schema?.remove("Bar")
    schema?.remove("Foo")

and when the migration was executed, my app crashed and I received the error message:

"Table is target of cross-table link columns"

I solved it by reversing the remove order

    schema?.remove("Foo")
    schema?.remove("Bar")

Because Foo depends on Bar, so if I removed it first, Foo would have an invalid field, because Bar wouldn't be a RealmObject anymore.

Hope this helps!

All 10 comments

Thanks for reporting this @anlaital!

@anlaital Currently, Realm cannot delete a model class that has backlink property and the class definition no longer exists when migration. The workaround is to keep leaving the model definition to be deleted.

@kishikawakatsumi so what would be the workaround when I have deleteRealmIfMigrationNeeded set to true with backlink properties in a model? We're still in our early days and love the deleteRealmIfMigrationNeeded feature for whenever we're throwing new properties in, but my app went into an eternal death spiral when I added a backlink.

I can see that this has been set as P2, but would it be possible to enhance the error message given in this scenario? Our current project has quite a few models, so just stating Table is target of cross-table link columns is not very helpful. Could this contain information on the offending class or table?

Hello,

I'm curious about this, I'm having the same issue when reworking ours migrations logics.
The first draft used to leave the tables for deleted objects after migrations inside the realm.

I though I would tried to improved it and got this migration logics:

let newClassNames = migration.newSchema.objectSchema.map { $0.className }

migration.oldSchema.objectSchema.forEach { (objectSchema) in
    guard newClassNames.contains(objectSchema.className) == false else {
        return
    }

    migration.deleteData(forType: objectSchema.className)
    print("Realm migration :: deleting removed type \(objectSchema.className)")
}

@anlaital proposition would be welcomed.
I'd even see this behaviour as a default for removed classes during migration to cleanup realm databases.

Not sure if I'm posting this in the correct issue as the issue I've submitted was marked a duplicate of this issue.

I've attempted to avoid the cross table link error by setting the config's deleteRealmIfMigrationNeeded to true. This works but means the persisted data is always deleted when a migration is needed.

Rather than always setting deleteRealmIfMigrationNeeded to true so I can do migrations, I'm looking into catching the cross table link error when initializing realm, and then attempting to initialize realm again with another configuration with deleteRealmIfMigrationNeeded set to true like so:

        let defaultConfig = Realm.Configuration()
        defaultConfig.schemaVersion = newSchemaVersion
        defaultConfig.migrationBlock = {
            // perform migrations...
        } 

        let realm: Realm
        do {
            realm = try Realm(configuration: defaultConfig)
        } catch {

            let deleteMigrationConfig = Realm.Configuration()
            deleteMigrationConfig.schemaVersion = newSchemaVersion
            deleteMigrationConfig.deleteRealmIfMigrationNeeded = true

            do {
                realm = try Realm(configuration: deleteMigrationConfig)
            } catch {
                fatalError("Failed to instantiate: \(error.localizedDescription)")
            }
        }

I'm getting the Table is target of cross-table link columns in the first catch like expected but I'm getting Realm at path '.../default.realm' already opened with a different schema mode. when it's performing the initialization in the second catch:

Why is my realm file opened if it has failed to be initialized?

I've got a related problem. I've moved a table from one table into a readonly/asset table and at migration time I do the following:

if (realm.getSchema().contains("SearchArea")) {
realm.getSchema().remove("SearchArea");
}

Even though it is no longer in the realm module definition. It does have self referential links (e.g., its hierarchical).

How should this be dealt with?

In case this is useful for anyone:
I had two classes:

class Foo: RealmObject(){
    val bar: Bar
    ...
}

and

class Bar: RealmObject {
    ...
}

I didn't need to store those in realm anymore, so in my Migration class I wrote the following:

    schema?.remove("Bar")
    schema?.remove("Foo")

and when the migration was executed, my app crashed and I received the error message:

"Table is target of cross-table link columns"

I solved it by reversing the remove order

    schema?.remove("Foo")
    schema?.remove("Bar")

Because Foo depends on Bar, so if I removed it first, Foo would have an invalid field, because Bar wouldn't be a RealmObject anymore.

Hope this helps!

@ivanebernal Can you please tell me what is the "remove" function? I can't find it in RLMSchema object, thanks.

EDIT:

I found the equivalent function in swift: https://realm.io/docs/swift/latest/api/Classes/Migration.html#/s:FC10RealmSwift9Migration10deleteDataFT7forTypeSS_Sb
You can check this question in SO:
https://stackoverflow.com/questions/36717796/how-to-delete-a-class-from-realm-file

Hope this helps!


@amoshsueh hello. My code is actually written in Kotlin (for android). The remove function can be found on the RealmSchema object of the DynamicRealm provided by the overridden migrate method in your RealmMigration implementation.
In other words:

//On your migrate method in your RealmMigration
realm?.schema?.remove("something")

Here's an example of a migration class:

class Migration : RealmMigration {
    override fun migrate(realm: DynamicRealm?, oldVersion: Long, newVersion: Long) {
        val schema = realm?.schema
        if(newVersion == 2L) {
          schema?.remove("YourRealmObject")
        }
    }
} 

where 'YourRealmObject' is the name of your class extending RealmObject.

An example of a migration in swift can be found here:
https://realm.io/docs/swift/latest/#updating-values

Hope this helps!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dmorrow picture dmorrow  路  3Comments

ciminuv picture ciminuv  路  3Comments

TheHmmka picture TheHmmka  路  3Comments

dennisgec picture dennisgec  路  3Comments

ishidakei picture ishidakei  路  3Comments