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

Add documentation about migrations to website

Open tarasmorskyi-mediamonks opened this issue 3 years ago • 4 comments

As AutomaticSchemaMigration is available for data migration from old schema to new one there is still no documentation for it.

It's possible to understand what to do with source code comments but I'm not fully sure if I'm doing it correctly as current result looks like super complicated and hard to read.

~Main issue there is handling DynamicRealmObjects, first for reading as I have to get child DynamicRealmObjectImpl instead of proper class and then extending DynamicRealmObject which creates lots of noise in class for inserting into new realm version.~ Haven't noticed comment for migration about data been kept if only new fields were added and nothing else which means I don't need to do anything there.

tarasmorskyi-mediamonks avatar Aug 30 '22 12:08 tarasmorskyi-mediamonks

Hi @tarasmorskyi-mediamonks. Thanks for the feedback, we will try to improve the docs. Here is a small sample snippet to assist grasping and combining the API/concepts in the meantime

val config = RealmConfiguration.Builder(
    schema = setOf(ModelClass::class)
)
.migration(AutomaticSchemaMigration {

    val oldRealm = it.oldRealm
    val newRealm = it.newRealm
    
    // Access old objects with the string based API as DynamicRealmObjects
    val oldObjects = oldRealm.query("ModelClass").find()
    for (oldObject in oldObjects) {
        val fieldValue: String = oldObject.getValue("fieldName", String::class)
        val child: DynamicRealmObject? = oldObject.getObject("childObjects")
    }

    // Access migrated objects as mutable objects in the migrated realm. Unmodified schema properties will be left as is
    val oldObjectInMigratedRealm: DynamicMutableRealmObject? =
        newRealm.findLatest(oldObjects[0])
    oldObjectInMigratedRealm?.let {
        it.set("fieldName", "new field value")
    }

    // Fast iteration of all objects in the old Realm
    it.enumerate("ModelClass") { oldObject: DynamicRealmObject, newObject: DynamicMutableRealmObject? ->
        // Some common use cases are highlighted at
        // https://www.mongodb.com/docs/realm-sdks/kotlin/1.0.2/library-base/-realm%20-kotlin%20-s-d-k/io.realm.kotlin.migration/-automatic-schema-migration/-migration-context/enumerate.html
    }

    // Creating of new objects from scratch in the migrated realm
    val scratchObjectInMigratedRealm = newRealm.copyToRealm(
        DynamicMutableRealmObject.create("ModelClass", mapOf("fieldName" to "scratch value"))
    )
}

Feel free to drop additional questions here, so that we can try to cover as much when updating the docs

rorbech avatar Aug 30 '22 15:08 rorbech

Thanks @rorbech for the provided examples. What is still not clear to me is how to transfer migrations from the java-sdk to the kotlin-sdk. We want to migrate to the kotlin-sdk and we are having hard times figuring out what is the correct way to do it. This so far is preventing us from giving it a go.

We have a bunch of migration blocks in the java-sdk variant like this:

object RealmMigrationHelper {
    
    // PUBLIC ⤵

    /**
     * Returns [RealmMigration] object that manages all migrations based on the old and new schema versions
     * You must add new if block everytime changes to the Realm classes are made
     * @return [RealmMigration]
     */
    fun migrate(): RealmMigration {

        return RealmMigration { realm, oldVersion, newVersion ->
            var version = oldVersion
            val schema: RealmSchema = realm.schema

            // Changes from schema version 0 to 1:
            if (version == 0L) {
                migrate0to1(schema)

                version++
            }

            // Changes from schema version 1 to 2:
            if (version == 1L) {
                migrate1to2(schema)

                version++
            }

            if (version == 2L) {
                migrate2to3(schema)

                version++
            }

            if (version == 3L) {
                migrate3to4(schema)

                version++
            }

            if (version == 4L) {
                migrate4to5(schema)

                version++
            }

            if (version == 5L) {
                migrate5to6(schema)

                version++
            }

            if (version == 6L) {
                migrate6to7(schema)

                version++
            }

            if (version == 7L) {
                migrate7to8(schema)

                version++
            }

            if (version == 8L) {
                migrate8to9(schema)

                version++
            }

            if (version == 9L) {
                migrate9to10(schema)

                version++
            }

            if (version == 10L) {
                migrate10to11(schema)

                version++
            }

            if (version == 11L) {
                migrate11to12(schema)

                version++
            }

            if (version == 12L) {
                migrate12to13(schema)

                version++
            }

            if (version == 13L) {
                migrate13to14(schema)

                version++
            }
        }
    }

    // PRIVATE ⤵
    
    /**
     * Migrates the database from version 0 to 1
     */
    private fun migrate0to1(schema: RealmSchema) {
        schema.get(RealmEntityLinks.TAG)
            ?.addField("name", String::class.javaObjectType)

        schema.get(RealmEntityFeatures.TAG)
            ?.addField("name", Boolean::class.javaObjectType)
    }
    
    /**
     * Migrates the database from version 2 to 3
     */
    private fun migrate1to2(schema: RealmSchema) {
        val detailsSchema = schema.get("RealmEntityResultDetails")
        val performerSchema = schema.get(RealmEntityPerformer.TAG)

        detailsSchema?.let { unwrappedDetailsSchema ->
            performerSchema?.let { unwrappedPerformerSchema ->
                unwrappedDetailsSchema
                    .removeField("performer")
                    .addRealmObjectField("performer", unwrappedPerformerSchema)
            }
        }
    }
    
    // And so on... until migrate13to14() function
    
    /**
     * Migrates the database from version 13 to 14
     * - Add field "linkingDate" to class [RealmEntityLinkedProfile]
     * - Add field "gender" to class [RealmEntityLinkedProfile]
     */
    private fun migrate13to14(schema: RealmSchema) {
        schema.get(RealmEntityLinkedProfile.TAG)
            ?.addField("linkingDate", String::class.javaObjectType)
            ?.addField("gender", Int::class.javaObjectType)
    }
}

Let's say we now updated all dependencies to reflect the ones required for having the kotlin-sdk up and running. How would the migration helper look like in the kotlin-sdk variant? In addition, should we increment the schema version when updating from java-sdk to kotlin-sdk? This also not clear.

Thanks in advance for your help.

brnmr avatar Mar 21 '24 14:03 brnmr