androidswiftrealmrealm-migration

How perform a better SwiftRealm migration value


I am using Realm Swift and Realm-java on iOS and Android. I need to implement database migrations to update values in databases. On Swift the migration is done as follows: currentVersion -> latestVersion. While on Android: v1 -> v2 -> latestVersion.

Following Example show migration 0 -> 1 and 1 -> 2 on iOS:

Migration 0 -> 1 :



    /// Function to migrate version 0 to version 1.
    /// Create Fantastic film category and associated 
    ///
    /// - Parameter migration: Migration instance.
    static func migrateFrom0To1(_ migration: Migration) {
        // Film to add for Fantastic category.
        let existingFilmToAdd: [String] = ["STAR_WARS", "HARRY_POTTER"]
        // Get stored films.
        var filmToSet: [MigrationObject] = []
        migration.enumerateObjects(ofType: Film.className()) { _, filmDb in
            let key = filmDb!["filmKey"] as! String
            if existingFilmToAdd.contains(key) {
                filmToSet.append(filmDb!)
            }
        }
        // Create Fiction Category.
        let fantasticCategory = migration.create(Category.className())
        fantasticCategory["categoryKey"] = "FANTASTIC"
        fantasticCategory["name"] = "Fantastic"
        fantasticCategory["films"] = filmToSet
    }

Migration 0/1 -> 2



    /// Function to migrate version 0 to version 1.
    /// Create Fantastic film category and associated 
    ///
    /// - Parameter migration: Migration instance.
    static func migrateFrom0To1(_ migration: Migration) {
        // Film to add for Fantastic category.
        let existingFilmToAdd: [String] = ["STAR_WARS", "HARRY_POTTER"]
        // Create Superman Film
        let superman = migration.create(Film.className())
        superman["filmKey"] = "SUPERMAN"
        superman["name"] = "Superman"
        superman["time"] = 2 
        // Get stored films.
        var filmToSet: [MigrationObject] = []
        migration.enumerateObjects(ofType: Film.className()) { _, filmDb in
            let key = filmDb!["filmKey"] as! String
            if existingFilmToAdd.contains(key) {
            filmToSet.append(filmDb!)
            }
        }
        filmToSet.append(superman)
        // Create Fiction Category.
        let fantasticCategory= migration.create(Category.className())
        fantasticCategory["categoryKey"] = "FANTASTIC"
        fantasticCategory["name"] = "Fantastic"
        fantasticCategory["films"] = filmToSet
    }
    
    /// Function to migrate version 1 to version 2.
    /// Add film in Fantastic Category 
    ///
    /// - Parameter migration: Migration instance.
    static func migrateFrom1To2(_ migration: Migration) {
        // Create Superman Film
        let superman = migration.create(Film.className())
        superman["filmKey"] = "SUPERMAN"
        superman["name"] = "Superman"
        superman["time"] = 2 
        // Get stored films.
        var filmToSet: [MigrationObject] = []
        // Update Fantastic film
        migration.enumerateObjects(ofType: Category.className()) { _, migrationCategory in
            let categoryKey = migrationMarket!["categoryKey"] as! String
            if categoryKey == "FANTASTIC" {
                let oldFilm = migrationCategory!["films"] as! List
                for film in oldFilm {
                   filmsToSet.append(film)
                }
                filmsToSet.append(superman)
                migrationCategory!["films"] = filmsToSet
            }
        }
    }

It's very easy on this example because there are simple data, but when we have a lot more value to add and modify it makes the migrations more and more complex.

On Android no problems, the migrations follow one another because the data is available in the database at the end of each migration functions. Migrations on swift are becoming more and more complex to implement.

I would like to make the Swift migration run like the Android migrations. After looking in the documentation and the different tickets, I can't find a solution. Do you have a solution or new architecture to solve the problem?

Note: The solution must always offer me the possibility to migrate the database schema


Solution

  • I found an answer : I proceed to multipe little migration by using performMigration in loop starting at oldSchemaVersion to new Schema version one by one.

    /// Define migration block.
    private let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in
        switch oldSchemaVersion {
        case 0:
            Migrations.migrateFrom0To1(migration)
        case 1:
            Migrations.migrateFrom1To2(migration)
        case 2:
            Migrations.migrateFrom2To3(migration)
        case 3:
            Migrations.migrateFrom3To4(migration)
        case 4:
            Migrations.migrateFrom4To5(migration)
        case 5:
            Migrations.migrateFrom5To6(migration)
        default:
            break
        }
    }
    
    /// Initialize realm configuration.
    ///
    /// - Throws: If error during realm configuration.
    private func initializeRealm(realmConfiguration: Realm.Configuration?) throws {
        // Init Realm configuration.
        var config: Realm.Configuration
        if realmConfiguration == nil {
            config = Realm.Configuration(schemaVersion: currentSchemaVersion)
            // Get current schema version of the database before migration.
            let oldSchemaVersion = try schemaVersionAtURL(config.fileURL!)
            print("oldSchemaVersion: \(oldSchemaVersion) currentSchemaVersion: \(currentSchemaVersion)")
    
            // Perform all migrations one by one
            // (old version to new version) and keep last config.
            if oldSchemaVersion < currentSchemaVersion {
                for tmpCurrentSchemaVersion in oldSchemaVersion...currentSchemaVersion {
                    config = Realm.Configuration(schemaVersion: tmpCurrentSchemaVersion, migrationBlock: migrationBlock)
                    try! Realm.performMigration(for: config)
                }
            }
        } else {
            config = realmConfiguration!
        }
    
        self.realm = try! Realm(configuration: config)
    }