Skip to content

Instantly share code, notes, and snippets.

@LOG-TAG
Forked from PrashamTrivedi/DbSchema.kt
Created April 29, 2018 15:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LOG-TAG/560755aeb68916a90b6944eccd2eb2b7 to your computer and use it in GitHub Desktop.
Save LOG-TAG/560755aeb68916a90b6944eccd2eb2b7 to your computer and use it in GitHub Desktop.
Some extension functions with room: Requires Export schema Read this section https://medium.com/google-developers/testing-room-migrations-be93cdb0d975#6872
import com.squareup.moshi.Json
data class DbSchema(@field:Json(name = "formatVersion") val formatVersion: Int? = 0, @field:Json(
name = "database") val database: Database? = Database())
data class Database(@field:Json(name = "version") val version: Int = 0, @field:Json(name = "identityHash") val identityHash: String? = "", @field:Json(
name = "entities") val entities: List<Entity?>? = listOf(), @field:Json(name = "setupQueries") val setupQueries: List<String?>? = listOf())
data class Entity(@field:Json(name = "tableName") val tableName: String? = "", @field:Json(name = "createSql") val createSql: String? = "", @field:Json(
name = "fields") val fields: List<Field?>? = listOf(), @field:Json(name = "primaryKey") val primaryKey: PrimaryKey? = PrimaryKey(), @field:Json(
name = "indices") val indices: List<Indice?>? = listOf(), @field:Json(name = "foreignKeys") val foreignKeys: List<ForeignKey?>? = listOf())
data class Indice(@field:Json(name = "name") val name: String? = "", @field:Json(name = "unique") val unique: Boolean? = false, @field:Json(
name = "columnNames") val columnNames: List<String?>? = listOf(), @field:Json(name = "createSql") val createSql: String? = "")
data class PrimaryKey(@field:Json(name = "columnNames") val columnNames: List<String?>? = listOf(), @field:Json(
name = "autoGenerate") val autoGenerate: Boolean? = false)
data class Field(@field:Json(name = "fieldPath") val fieldPath: String? = "", @field:Json(name = "columnName") val columnName: String? = "", @field:Json(
name = "affinity") val affinity: String? = "", @field:Json(name = "notNull") val notNull: Boolean? = false)
data class ForeignKey(@Json(name = "table") val table: String? = "", @Json(name = "onDelete") val onDelete: String? = "", @Json(
name = "onUpdate") val onUpdate: String? = "", @Json(name = "columns") val columns: List<String?>? = listOf(), @Json(
name = "referencedColumns") val referencedColumns: List<String?>? = listOf())
//Callback DSLs
Room.databaseBuilder(application,
AppDatabase::class.java,
"Demo.db").onCreate { db: SupportSQLiteDatabase ->
}.onOpen { db: SupportSQLiteDatabase -> }.addMigration(oldVersion = 1, newVersion = 2) {db:SupportSQLiteDatabase->
val migrationUtils = MigrationUtils(db,
context = application.applicationContext,
moshi = moshi)
val migrationStatements = migrationUtils.getMigrationStatements("com.zoomi.globalretailer.db.AppDatabase/2.json")
migrationStatements.forEach {
db.execSQL(it)
}
}.build()
//Dagger example from Production App
@Provides
@AppScope
fun providesAppDB(application: GlobalRetailerApp, moshi: Moshi) = Room.databaseBuilder(
application,
AppDatabase::class.java,
"Demo.db").addMigrationList(MigrationUtils(application.applicationContext,
moshi).getMigrations()).build()
//To make this class work, schema must be exported.
// To export the schema, follow Prerequisites section from below link
//https://medium.com/google-developers/testing-room-migrations-be93cdb0d975#6872
class MigrationUtils(val context: Context, val moshi: Moshi) {
private fun getSchemas(): MutableList<DbSchema> {
Timber.tag("Schemas")
val name = AppDatabase::class.java.name
Timber.d(name)
val path = this@MigrationUtils.context.assets.list(name)
Timber.tag("Schemas")
Timber.d(path.joinToString())
val tableInfoArray = mutableListOf<DbSchema>()
path.map { getTableInfoFromJson("$name/$it") }.forEach {
it?.let {
tableInfoArray += it
}
}
return tableInfoArray
}
private fun getTableJson(fileName: String): String = this@MigrationUtils.context.readAssetJson(
fileName = fileName)
private fun getTableInfoFromJson(fileName: String): DbSchema? {
val jsonString = getTableJson(fileName)
return jsonString.fromJson<DbSchema>(moshi)
}
fun getMigrations(): List<Migration> {
val migrations = mutableListOf<Migration>()
val dbSchemas = getSchemas()
dbSchemas.reduce { acc, dbSchema ->
if (acc.database != null && dbSchema.database != null) {
migrations += getMigrationStatement(acc.database, dbSchema.database)
}
dbSchema
}
return migrations
}
private fun getMigrationStatement(first: Database, second: Database): Migration {
val statementsToExecute = mutableListOf<String>()
val firstEntities = first.entities ?: listOf()
val secondEntities = second.entities ?: listOf()
for (entity in secondEntities) {
entity?.let { (tableName, _, fields, _, indices) ->
val firstEntity = firstEntities.first { tableName == it?.tableName }
//Compare Columns
val firstColumns = firstEntity?.fields ?: listOf()
val secondColumns = fields ?: listOf()
val newColumns = secondColumns.filterNot { firstColumns.contains(it) }
//Add new Columns
for (column in newColumns) {
column?.let {
val constraint = if (it.notNull == true) "NOT NULL DEFAULT ${getDefaultValue(
it.affinity)}" else ""
val migrationStatement = "Alter table '$tableName' Add column '${it.columnName}' ${it.affinity} $constraint"
Timber.d(migrationStatement)
statementsToExecute += migrationStatement
}
}
//Indices
val firstIndices = firstEntity?.indices ?: listOf()
val secondIndices = indices ?: listOf()
val newIndices = secondIndices.filterNot { firstIndices.contains(it) }
for (newIndex in newIndices) {
newIndex?.let {
val migrationStatementDrop = "Drop Index ${it.name}"
val migrationStatementReCreate = it.createSql ?: ""
Timber.d(migrationStatementDrop)
Timber.d(migrationStatementReCreate)
statementsToExecute += migrationStatementDrop
statementsToExecute += migrationStatementReCreate
}
}
}
}
return object : Migration(first.version, second.version) {
override fun migrate(database: SupportSQLiteDatabase) {
try {
for (statement in statementsToExecute) {
Timber.tag("Statement")
Timber.w(statement)
database.execSQL(statement)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
private fun getDefaultValue(affinity: String?): String = when (affinity ?: "") {
"NUMBER" -> "0"
"TEXT" -> "''"
else -> "''"
}
}
inline fun <T : RoomDatabase?> RoomDatabase.Builder<T>.callback(crossinline onCreateFun: (SupportSQLiteDatabase) -> Unit = {},
crossinline onOpenFun: (SupportSQLiteDatabase) -> Unit = {}): RoomDatabase.Builder<T> {
addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
onCreateFun(db)
super.onCreate(db)
}
override fun onOpen(db: SupportSQLiteDatabase) {
onOpenFun(db)
super.onOpen(db)
}
})
return this
}
inline fun <T : RoomDatabase?> RoomDatabase.Builder<T>.onCreate(crossinline onCreateFun: (SupportSQLiteDatabase) -> Unit = {}): RoomDatabase.Builder<T> = callback(
onCreateFun = onCreateFun)
inline fun <T : RoomDatabase?> RoomDatabase.Builder<T>.onOpen(crossinline onOpenFun: (SupportSQLiteDatabase) -> Unit = {}): RoomDatabase.Builder<T> = callback(
onOpenFun = onOpenFun)
inline fun <T : RoomDatabase> RoomDatabase.Builder<T>.addMigration(oldVersion: Int,
newVersion: Int,
crossinline migrateFun: (SupportSQLiteDatabase) -> Unit = {}): RoomDatabase.Builder<T> {
return addMigrations(object : Migration(oldVersion, newVersion) {
override fun migrate(database: SupportSQLiteDatabase) {
migrateFun(database)
}
})
}
fun <T : RoomDatabase> RoomDatabase.Builder<T>.addMigrationList(migrations: List<Migration>): RoomDatabase.Builder<T> = addMigrations(*migrations.toTypedArray())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment