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
}
}
}
If app is already installed in V1, migration 1 -> 2 will be executed, it's easy, this migration create a new Superman film and add it into Fantastic Category previously stored into database.
If it is a first installation: 0 -> 2 will be executed. Fantastic Category is created in 0 -> 1 but not available yet in migration 1 -> 2 because migration is not finished. I have to modify the function 0 -> 1 migration function to create Superman film and add to Fantastic Category at the creation.
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
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)
}