realm-swift icon indicating copy to clipboard operation
realm-swift copied to clipboard

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

Open anlaital opened this issue 8 years ago • 11 comments

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

anlaital avatar Jun 01 '16 07:06 anlaital

Thanks for reporting this @anlaital!

jpsim avatar Jun 01 '16 22:06 jpsim

@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 avatar Jun 02 '16 08:06 kishikawakatsumi

@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.

iantheparker avatar Sep 29 '16 15:09 iantheparker

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?

anlaital avatar Feb 13 '17 10:02 anlaital

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.

lifely avatar Jan 22 '18 17:01 lifely

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?

fenroar avatar Feb 16 '18 15:02 fenroar

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?

KennyGoers avatar Jun 26 '18 21:06 KennyGoers

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 avatar Aug 29 '18 21:08 ivanebernal

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

amoshsueh avatar Dec 13 '18 16:12 amoshsueh

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!

ivanebernal avatar Dec 14 '18 01:12 ivanebernal

I still face the problem

class Model { (...) specificState: SpecificState } class SubObject { (...) parent: Model }

Realm configuration's object type contains SpecificState.self

I want to remove SubObject from my app, but I cannot manage to perform a migration that would at the same time remove the class & the data. deleteData(forType: "SubObject") seems to be the one with the issue.

If I don't call deleteData(...) , the only issue is that the data is remaining.

I don't want to need to create two configuration, I aspect a migration to be able to do so.

quentinfasquel avatar Jan 10 '22 16:01 quentinfasquel