AutoMigrationWriter.kt
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.room.writer
import androidx.annotation.NonNull
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.addOriginatingElement
import androidx.room.ext.L
import androidx.room.ext.RoomTypeNames
import androidx.room.ext.S
import androidx.room.ext.SupportDbTypeNames
import androidx.room.vo.AutoMigrationResult
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import javax.lang.model.element.Modifier
/**
* Writes the implementation of migrations that were annotated with @AutoMigration.
*/
class AutoMigrationWriter(
private val dbElement: XElement,
val autoMigrationResult: AutoMigrationResult
) : ClassWriter(autoMigrationResult.element.className) {
override fun createTypeSpecBuilder(): TypeSpec.Builder {
val builder = TypeSpec.classBuilder(autoMigrationResult.implTypeName)
builder.apply {
addOriginatingElement(dbElement)
// TODO: (b/180395129) Force user to extend an AutoMigration interface. For now it
// should extend user annotated class but when the bug is resolved, the generated
// code will extract the versions from the AutoMigration annotation and call the
// super constructor since the generated class will extend Room's Migration class.
superclass(RoomTypeNames.MIGRATION)
addMethod(createConstructor())
addMethod(createMigrateMethod())
}
return builder
}
private fun createMigrateMethod(): MethodSpec? {
val migrateFunctionBuilder: MethodSpec.Builder = MethodSpec.methodBuilder("migrate")
.apply {
addParameter(
ParameterSpec.builder(
SupportDbTypeNames.DB,
"database"
).addAnnotation(NonNull::class.java).build()
)
addAnnotation(Override::class.java)
addModifiers(Modifier.PUBLIC)
returns(TypeName.VOID)
addAutoMigrationResultToMigrate(this)
}
return migrateFunctionBuilder.build()
}
/**
* Takes the changes provided in the {@link AutoMigrationResult} which are differences detected
* between the two versions of the same database, and converts them to the appropriate
* sequence of SQL statements that migrate the database from one version to the other.
*
* @param migrateFunctionBuilder Builder for the migrate() function to be generated
*/
private fun addAutoMigrationResultToMigrate(migrateFunctionBuilder: MethodSpec.Builder) {
if (autoMigrationResult.addedColumns.isNotEmpty()) {
addNewColumnStatements(migrateFunctionBuilder)
}
}
/**
* Adds the appropriate SQL statements for adding new columns to a table, into the
* generated migrate() function.
*
* @param migrateFunctionBuilder Builder for the migrate() function to be generated
*/
private fun addNewColumnStatements(migrateFunctionBuilder: MethodSpec.Builder) {
autoMigrationResult.addedColumns.forEach {
val addNewColumnSql = buildString {
append(
"ALTER TABLE '${it.tableName}' ADD COLUMN `${it.fieldBundle.columnName}` " +
"${it.fieldBundle.affinity} "
)
if (it.fieldBundle.isNonNull) {
append("NOT NULL DEFAULT '${it.fieldBundle.defaultValue}'")
} else {
append("DEFAULT NULL")
}
}
migrateFunctionBuilder.addStatement("database.execSQL($S)", addNewColumnSql)
}
}
/**
* Builds the constructor of the generated AutoMigration.
*
* @return The constructor of the generated AutoMigration
*/
private fun createConstructor(): MethodSpec {
return MethodSpec.constructorBuilder().apply {
addModifiers(Modifier.PUBLIC)
addStatement("super($L, $L)", autoMigrationResult.from, autoMigrationResult.to)
}.build()
}
}