Implementing SQL statements needed for performing complex schema changes.
This CL implements generating the migrate() function that accounts for all schema changes except for table/column renames/deletes and changes involving FTS tables. The follow-up CL will account for these remaining changes.
In addition, changes have been made in RoomDatabase to provide the user the ability to get a particular generated autoMigration in the form of a Migration object, by specifying the "from" and "to" versions of the desired autoMigration. This is a workaround so that users can test their autoMigrations using the MigrationTestHelper API's runMigrationsAndValidate() function. If the user requests an autoMigration that has not been generated, RoomDatabase will throw an IllegalArgumentException.
Test: The AutoMigrationTest.kt migrates the database from version 1 to version 2. The schemas at these two different version involve a wide variety of schema changes performed, ranging from a simple change as adding a new table to changing an existing index.
Bug: 180395129
Change-Id: Ia1f3ca7db5debf664a0791e8a3f93d9ea68bc966
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
index d8818644..1a8aa08 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
@@ -104,14 +104,11 @@
return null
}
- // TODO: (b/183434667) Update the automigration result data object to handle complex
- // schema changes' presence when writer code is introduced
return AutoMigrationResult(
element = element,
from = fromSchemaBundle.version,
to = toSchemaBundle.version,
- addedColumns = schemaDiff.addedColumns,
- addedTables = schemaDiff.addedTables
+ schemaDiff = schemaDiff
)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index a62aac8..08054a6 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -864,4 +864,9 @@
val AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF = "Cannot create auto-migrations when export " +
"schema is OFF."
-}
\ No newline at end of file
+
+ fun tableWithNewTablePrefixFound(tableName: String): String {
+ return "The new version of the schema contains `$tableName` a table name" +
+ " with the prefix '_new_', which is causing a conflict during autoMigration."
+ }
+}
diff --git a/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt b/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt
index 53f1f231..2208483 100644
--- a/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt
@@ -20,7 +20,10 @@
import androidx.room.migration.bundle.EntityBundle
import androidx.room.migration.bundle.ForeignKeyBundle
import androidx.room.migration.bundle.IndexBundle
-import androidx.room.processor.ProcessorErrors
+import androidx.room.processor.ProcessorErrors.newNotNullColumnMustHaveDefaultValue
+import androidx.room.processor.ProcessorErrors.removedOrRenamedColumnFound
+import androidx.room.processor.ProcessorErrors.removedOrRenamedTableFound
+import androidx.room.processor.ProcessorErrors.tableWithNewTablePrefixFound
import androidx.room.vo.AutoMigrationResult
/**
@@ -32,12 +35,11 @@
* Contains the added, changed and removed columns detected.
*/
data class SchemaDiffResult(
- val addedColumns: List<AutoMigrationResult.AddedColumn>,
- val changedColumns: List<AutoMigrationResult.ChangedColumn>,
- val removedColumns: List<AutoMigrationResult.RemovedColumn>,
+ val addedColumns: Map<String, AutoMigrationResult.AddedColumn>,
+ val removedOrRenamedColumns: List<AutoMigrationResult.RemovedOrRenamedColumn>,
val addedTables: List<AutoMigrationResult.AddedTable>,
- val complexChangedTables: List<AutoMigrationResult.ComplexChangedTable>,
- val removedTables: List<AutoMigrationResult.RemovedTable>
+ val complexChangedTables: Map<String, AutoMigrationResult.ComplexChangedTable>,
+ val removedOrRenamedTables: List<AutoMigrationResult.RemovedOrRenamedTable>
)
/**
@@ -59,39 +61,56 @@
*/
fun diffSchemas(): SchemaDiffResult {
val addedTables = mutableListOf<AutoMigrationResult.AddedTable>()
- val complexChangedTables = mutableListOf<AutoMigrationResult.ComplexChangedTable>()
- val removedTables = mutableListOf<AutoMigrationResult.RemovedTable>()
+ val complexChangedTables = mutableMapOf<String, AutoMigrationResult.ComplexChangedTable>()
+ val removedOrRenamedTables = mutableListOf<AutoMigrationResult.RemovedOrRenamedTable>()
- val addedColumns = mutableListOf<AutoMigrationResult.AddedColumn>()
- val changedColumns = mutableListOf<AutoMigrationResult.ChangedColumn>()
- val removedColumns = mutableListOf<AutoMigrationResult.RemovedColumn>()
+ val addedColumns = mutableMapOf<String, AutoMigrationResult.AddedColumn>()
+ val removedOrRenamedColumns = mutableListOf<AutoMigrationResult.RemovedOrRenamedColumn>()
// Check going from the original version of the schema to the new version for changed and
// removed columns/tables
fromSchemaBundle.entitiesByTableName.forEach { fromTable ->
val toTable = toSchemaBundle.entitiesByTableName[fromTable.key]
if (toTable == null) {
- removedTables.add(AutoMigrationResult.RemovedTable(fromTable.value))
+ // TODO: (b/183007590) When handling renames, check if a complex changed table
+ // exists for the renamed table. If so, edit the entry to
+ // reflect the rename. If not, create a new
+ // RenamedTable object to be handled by addSimpleChangeStatements().
+ removedOrRenamedTables.add(
+ AutoMigrationResult.RemovedOrRenamedTable(fromTable.value)
+ )
} else {
val complexChangedTable = tableContainsComplexChanges(fromTable.value, toTable)
if (complexChangedTable != null) {
- complexChangedTables.add(complexChangedTable)
+ complexChangedTables[complexChangedTable.tableName] = complexChangedTable
}
val fromColumns = fromTable.value.fieldsByColumnName
val toColumns = toTable.fieldsByColumnName
fromColumns.entries.forEach { fromColumn ->
val match = toColumns[fromColumn.key]
- if (match != null && !match.isSchemaEqual(fromColumn.value)) {
- // Any change in the field bundle schema of a column will be complex
- changedColumns.add(
- AutoMigrationResult.ChangedColumn(
- fromTable.key,
- fromColumn.value
+ if (match != null && !match.isSchemaEqual(fromColumn.value) &&
+ !complexChangedTables.containsKey(fromTable.key)
+ ) {
+ if (toSchemaBundle.entitiesByTableName.containsKey(toTable.newTableName)) {
+ // TODO: (b/183975119) Use another prefix automatically in these cases
+ diffError(tableWithNewTablePrefixFound(toTable.newTableName))
+ }
+ complexChangedTables[fromTable.key] =
+ AutoMigrationResult.ComplexChangedTable(
+ tableName = fromTable.key,
+ newTableName = toTable.newTableName,
+ oldVersionEntityBundle = fromTable.value,
+ newVersionEntityBundle = toTable,
+ foreignKeyChanged = false,
+ indexChanged = false
)
- )
} else if (match == null) {
- removedColumns.add(
- AutoMigrationResult.RemovedColumn(
+ // TODO: (b/183007590) When handling renames, check if a complex changed
+ // table exists for the table of the renamed column. If so, edit the
+ // entry to reflect the column rename. If not, create a new
+ // RenamedColumn object to be handled by addSimpleChangeStatements().
+ removedOrRenamedColumns.add(
+ AutoMigrationResult.RemovedOrRenamedColumn(
fromTable.key,
fromColumn.value
)
@@ -115,56 +134,38 @@
if (match == null) {
if (toColumn.value.isNonNull && toColumn.value.defaultValue == null) {
diffError(
- ProcessorErrors.newNotNullColumnMustHaveDefaultValue(toColumn.key)
+ newNotNullColumnMustHaveDefaultValue(toColumn.key)
)
}
- addedColumns.add(
- AutoMigrationResult.AddedColumn(
- toTable.key,
- toColumn.value
- )
- )
+ // Check if the new column is on a table with complex changes. If so, no
+ // need to account for it as the table will be recreated with the new
+ // table already.
+ if (!complexChangedTables.containsKey(toTable.key)) {
+ addedColumns[toColumn.value.columnName] =
+ AutoMigrationResult.AddedColumn(
+ toTable.key,
+ toColumn.value
+ )
+ }
}
}
}
}
- // TODO: (b/183007590) Remove the Processor Errors thrown when a complex change is
- // encountered after AutoMigrationWriter supports generating the necessary migrations.
- if (changedColumns.isNotEmpty()) {
- changedColumns.forEach { changedColumn ->
+ if (removedOrRenamedColumns.isNotEmpty()) {
+ removedOrRenamedColumns.forEach { removedColumn ->
diffError(
- ProcessorErrors.columnWithChangedSchemaFound(
- changedColumn.fieldBundle.columnName
- )
- )
- }
- }
-
- if (removedColumns.isNotEmpty()) {
- removedColumns.forEach { removedColumn ->
- diffError(
- ProcessorErrors.removedOrRenamedColumnFound(
+ removedOrRenamedColumnFound(
removedColumn.fieldBundle.columnName
)
)
}
}
- if (complexChangedTables.isNotEmpty()) {
- complexChangedTables.forEach { changedTable ->
+ if (removedOrRenamedTables.isNotEmpty()) {
+ removedOrRenamedTables.forEach { removedTable ->
diffError(
- ProcessorErrors.tableWithComplexChangedSchemaFound(
- changedTable.tableName
- )
- )
- }
- }
-
- if (removedTables.isNotEmpty()) {
- removedTables.forEach { removedTable ->
- diffError(
- ProcessorErrors.removedOrRenamedTableFound(
+ removedOrRenamedTableFound(
removedTable.entityBundle.tableName
)
)
@@ -173,11 +174,10 @@
return SchemaDiffResult(
addedColumns = addedColumns,
- changedColumns = changedColumns,
- removedColumns = removedColumns,
+ removedOrRenamedColumns = removedOrRenamedColumns,
addedTables = addedTables,
complexChangedTables = complexChangedTables,
- removedTables = removedTables
+ removedOrRenamedTables = removedOrRenamedTables
)
}
@@ -201,8 +201,14 @@
val primaryKeyChanged = !fromTable.primaryKey.isSchemaEqual(toTable.primaryKey)
if (primaryKeyChanged || foreignKeyChanged || indexChanged) {
+ if (toSchemaBundle.entitiesByTableName.containsKey(toTable.newTableName)) {
+ diffError(tableWithNewTablePrefixFound(toTable.newTableName))
+ }
return AutoMigrationResult.ComplexChangedTable(
tableName = toTable.tableName,
+ newTableName = toTable.newTableName,
+ oldVersionEntityBundle = fromTable,
+ newVersionEntityBundle = toTable,
foreignKeyChanged = foreignKeyChanged,
indexChanged = indexChanged
)
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
index f6fcc1a..ba352db 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
@@ -19,14 +19,17 @@
import androidx.room.compiler.processing.XTypeElement
import androidx.room.migration.bundle.EntityBundle
import androidx.room.migration.bundle.FieldBundle
+import androidx.room.util.SchemaDiffResult
import com.squareup.javapoet.ClassName
+/**
+ * Stores the changes detected in a database schema between the old and new versions.
+ */
data class AutoMigrationResult(
val element: XTypeElement,
val from: Int?,
val to: Int?,
- val addedColumns: List<AddedColumn>,
- val addedTables: List<AddedTable>
+ val schemaDiff: SchemaDiffResult
) {
val implTypeName: ClassName by lazy {
@@ -43,16 +46,6 @@
data class AddedColumn(val tableName: String, val fieldBundle: FieldBundle)
/**
- * Stores the table name and the relevant field bundle of a column that was present in both
- * the old and new version of the same database, but had a change in the field schema (e.g.
- * change in affinity).
- */
- data class ChangedColumn(
- val tableName: String,
- val fieldBundle: FieldBundle
- )
-
- /**
* Stores the table name and the relevant field bundle of a column that was present in the
* old version of a database but is not present in a new version of the same database, either
* because it was removed or renamed.
@@ -60,7 +53,7 @@
* In the current implementation, we cannot differ between whether the column was removed or
* renamed.
*/
- data class RemovedColumn(val tableName: String, val fieldBundle: FieldBundle)
+ data class RemovedOrRenamedColumn(val tableName: String, val fieldBundle: FieldBundle)
/**
* Stores the table that was added to a database in a newer version.
@@ -82,6 +75,9 @@
*/
data class ComplexChangedTable(
val tableName: String,
+ val newTableName: String,
+ val oldVersionEntityBundle: EntityBundle,
+ val newVersionEntityBundle: EntityBundle,
val foreignKeyChanged: Boolean,
val indexChanged: Boolean
)
@@ -93,5 +89,5 @@
* In the current implementation, we cannot differ between whether the table was removed or
* renamed.
*/
- data class RemovedTable(val entityBundle: EntityBundle)
+ data class RemovedOrRenamedTable(val entityBundle: EntityBundle)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
index 0661aa1..9748802 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -33,10 +33,16 @@
/**
* Writes the implementation of migrations that were annotated with @AutoMigration.
*/
+// TODO: (b/181777611) Handle FTS tables
class AutoMigrationWriter(
private val dbElement: XElement,
val autoMigrationResult: AutoMigrationResult
) : ClassWriter(autoMigrationResult.implTypeName) {
+ val addedColumns = autoMigrationResult.schemaDiff.addedColumns
+ val removedOrRenamedColumns = autoMigrationResult.schemaDiff.removedOrRenamedColumns
+ val addedTables = autoMigrationResult.schemaDiff.addedTables
+ val complexChangedTables = autoMigrationResult.schemaDiff.complexChangedTables
+ val removedOrRenamedTables = autoMigrationResult.schemaDiff.removedOrRenamedTables
override fun createTypeSpecBuilder(): TypeSpec.Builder {
val builder = TypeSpec.classBuilder(autoMigrationResult.implTypeName)
@@ -50,6 +56,18 @@
return builder
}
+ /**
+ * 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()
+ }
+
private fun createMigrateMethod(): MethodSpec? {
val migrateFunctionBuilder: MethodSpec.Builder = MethodSpec.methodBuilder("migrate")
.apply {
@@ -73,63 +91,198 @@
* 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
+ * @param migrateBuilder Builder for the migrate() function to be generated
*/
- private fun addAutoMigrationResultToMigrate(migrateFunctionBuilder: MethodSpec.Builder) {
- if (autoMigrationResult.addedTables.isNotEmpty()) {
- addNewTableStatements(migrateFunctionBuilder)
+ private fun addAutoMigrationResultToMigrate(migrateBuilder: MethodSpec.Builder) {
+ if (complexChangedTables.isNotEmpty()) {
+ addComplexChangeStatements(migrateBuilder)
}
- if (autoMigrationResult.addedColumns.isNotEmpty()) {
- addNewColumnStatements(migrateFunctionBuilder)
+ addSimpleChangeStatements(migrateBuilder)
+ }
+
+ /**
+ * Adds SQL statements performing schema altering commands that are not directly supported by
+ * SQLite (e.g. foreign key changes). These changes are referred to as "complex" changes.
+ *
+ * @param migrateBuilder Builder for the migrate() function to be generated
+ */
+ private fun addComplexChangeStatements(migrateBuilder: MethodSpec.Builder) {
+ val tablesToProcess = complexChangedTables
+ tablesToProcess.forEach { table ->
+ // TODO: (b/183007590) Check for column / table renames here before processing
+ // complex changes
+ addStatementsToCreateNewTable(table.value, migrateBuilder)
+ addStatementsToContentTransfer(table.value, migrateBuilder)
+ addStatementsToDropTableAndRenameTempTable(table.value, migrateBuilder)
+ addStatementsToRecreateIndexes(table.value, migrateBuilder)
+ addStatementsToCheckForeignKeyConstraint(table.value, migrateBuilder)
}
}
/**
- * Adds the appropriate SQL statements for adding new columns to a table, into the
- * generated migrate() function.
+ * Adds SQL statements performing schema altering commands directly supported by SQLite
+ * (adding tables/columns, renaming tables/columns, dropping tables/columns). These changes
+ * are referred to as "simple" changes.
*
- * @param migrateFunctionBuilder Builder for the migrate() function to be generated
+ * @param migrateBuilder Builder for the migrate() function to be generated
*/
- private fun addNewColumnStatements(migrateFunctionBuilder: MethodSpec.Builder) {
- autoMigrationResult.addedColumns.forEach {
- val addNewColumnSql = buildString {
+ // TODO: (b/183007590) Handle column/table renames here
+ private fun addSimpleChangeStatements(migrateBuilder: MethodSpec.Builder) {
+
+ if (addedColumns.isNotEmpty()) {
+ addNewColumnStatements(migrateBuilder)
+ }
+
+ if (addedTables.isNotEmpty()) {
+ addNewTableStatements(migrateBuilder)
+ }
+ }
+
+ /**
+ * Adds the SQL statements for creating a new table in the desired revised format of table.
+ *
+ * @param migrateBuilder Builder for the migrate() function to be generated
+ */
+ private fun addStatementsToCreateNewTable(
+ table: AutoMigrationResult.ComplexChangedTable,
+ migrateBuilder: MethodSpec.Builder
+ ) {
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ table.newVersionEntityBundle.createNewTable()
+ )
+ }
+
+ /**
+ * Adds the SQL statements for transferring the contents of the old table to the new version.
+ *
+ * @param migrateBuilder Builder for the migrate() function to be generated
+ */
+ private fun addStatementsToContentTransfer(
+ table: AutoMigrationResult.ComplexChangedTable,
+ migrateBuilder: MethodSpec.Builder
+ ) {
+ // TODO: (b/183007590) Account for renames and deletes here as ordering is important.
+ val oldColumnSequence = table.oldVersionEntityBundle.fieldsByColumnName.keys
+ .joinToString(",") { "`$it`" }
+ val newColumnSequence = (
+ table.newVersionEntityBundle.fieldsByColumnName.keys - addedColumns.keys
+ ).joinToString(",") { "`$it`" }
+
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ buildString {
append(
- "ALTER TABLE `${it.tableName}` ADD COLUMN `${it.fieldBundle.columnName}` " +
- "${it.fieldBundle.affinity} "
+ "INSERT INTO `${table.newTableName}` ($newColumnSequence) " +
+ "SELECT $oldColumnSequence FROM `${table.tableName}`"
)
- if (it.fieldBundle.isNonNull) {
- append("NOT NULL DEFAULT ${it.fieldBundle.defaultValue}")
- } else {
- append("DEFAULT NULL")
- }
}
- migrateFunctionBuilder.addStatement("database.execSQL($S)", addNewColumnSql)
- }
+ )
}
/**
- * Adds the appropriate SQL statements for adding new tables to a database, into the
- * generated migrate() function.
+ * Adds the SQL statements for dropping the table at the old version and renaming the
+ * temporary table to the name of the original table.
*
- * @param migrateFunctionBuilder Builder for the migrate() function to be generated
+ * @param migrateBuilder Builder for the migrate() function to be generated
*/
- private fun addNewTableStatements(migrateFunctionBuilder: MethodSpec.Builder) {
- autoMigrationResult.addedTables.forEach { addedTable ->
- migrateFunctionBuilder.addStatement(
- "database.execSQL($S)", addedTable.entityBundle.createTable()
+ private fun addStatementsToDropTableAndRenameTempTable(
+ table: AutoMigrationResult.ComplexChangedTable,
+ migrateBuilder: MethodSpec.Builder
+ ) {
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ "DROP TABLE `${table.tableName}`"
+ )
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ "ALTER TABLE `${table.newTableName}` RENAME TO `${table.tableName}`"
+ )
+ }
+
+ /**
+ * Adds the SQL statements for recreating indexes.
+ *
+ * @param migrateBuilder Builder for the migrate() function to be generated
+ */
+ private fun addStatementsToRecreateIndexes(
+ table: AutoMigrationResult.ComplexChangedTable,
+ migrateBuilder: MethodSpec.Builder
+ ) {
+ table.newVersionEntityBundle.indices.forEach { index ->
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ index.getCreateSql(table.tableName)
)
}
}
/**
- * Builds the constructor of the generated AutoMigration.
+ * Adds the SQL statement for checking the foreign key constraints.
*
- * @return The constructor of the generated AutoMigration
+ * @param migrateBuilder Builder for the migrate() function to be generated
*/
- private fun createConstructor(): MethodSpec {
- return MethodSpec.constructorBuilder().apply {
- addModifiers(Modifier.PUBLIC)
- addStatement("super($L, $L)", autoMigrationResult.from, autoMigrationResult.to)
- }.build()
+ private fun addStatementsToCheckForeignKeyConstraint(
+ table: AutoMigrationResult.ComplexChangedTable,
+ migrateBuilder: MethodSpec.Builder
+ ) {
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ "PRAGMA foreign_key_check(`${table.tableName}`)"
+ )
+ }
+
+ /**
+ * Adds the SQL statements for adding new columns to a table.
+ *
+ * @param migrateBuilder Builder for the migrate() function to be generated
+ */
+ private fun addNewColumnStatements(migrateBuilder: MethodSpec.Builder) {
+ addedColumns.forEach {
+ val addNewColumnSql = buildString {
+ append(
+ "ALTER TABLE `${it.value.tableName}` ADD COLUMN `${it.key}` " +
+ "${it.value.fieldBundle.affinity} "
+ )
+ if (it.value.fieldBundle.isNonNull) {
+ append("NOT NULL DEFAULT ${it.value.fieldBundle.defaultValue}")
+ } else {
+ append("DEFAULT NULL")
+ }
+ }
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ addNewColumnSql
+ )
+ }
+ }
+
+ /**
+ * Adds the SQL statements for adding new tables to a database.
+ *
+ * @param migrateBuilder Builder for the migrate() function to be generated
+ */
+ private fun addNewTableStatements(migrateBuilder: MethodSpec.Builder) {
+ addedTables.forEach { addedTable ->
+ addDatabaseExecuteSqlStatement(
+ migrateBuilder,
+ addedTable.entityBundle.createTable()
+ )
+ }
+ }
+
+ /**
+ * Adds the given SQL statements into the generated migrate() function to be executed by the
+ * database.
+ *
+ * @param migrateBuilder Builder for the migrate() function to be generated
+ */
+ private fun addDatabaseExecuteSqlStatement(
+ migrateBuilder: MethodSpec.Builder,
+ sql: String
+ ) {
+ migrateBuilder.addStatement(
+ "database.execSQL($S)", sql
+ )
}
}
diff --git a/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt b/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
index 94ed63a..402e271 100644
--- a/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
@@ -32,47 +32,36 @@
@Test
fun testPrimaryKeyChanged() {
- try {
- SchemaDiffer(
- fromSchemaBundle = from.database,
- toSchemaBundle = toChangeInPrimaryKey.database
- ).diffSchemas()
- fail("DiffException should have been thrown.")
- } catch (ex: DiffException) {
- assertThat(ex.errorMessage).isEqualTo(
- ProcessorErrors.tableWithComplexChangedSchemaFound("Song")
- )
- }
+ val diffResult = SchemaDiffer(
+ fromSchemaBundle = from.database,
+ toSchemaBundle = toChangeInPrimaryKey.database
+ ).diffSchemas()
+
+ assertThat(diffResult.complexChangedTables.keys).contains("Song")
}
@Test
fun testForeignKeyFieldChanged() {
- try {
- SchemaDiffer(
- fromSchemaBundle = from.database,
- toSchemaBundle = toForeignKeyAdded.database
- ).diffSchemas()
- fail("DiffException should have been thrown.")
- } catch (ex: DiffException) {
- assertThat(ex.errorMessage).isEqualTo(
- ProcessorErrors.tableWithComplexChangedSchemaFound("Song")
- )
- }
+ val diffResult = SchemaDiffer(
+ fromSchemaBundle = from.database,
+ toSchemaBundle = toForeignKeyAdded.database
+ ).diffSchemas()
+
+ assertThat(diffResult.complexChangedTables.isNotEmpty())
+ assertThat(diffResult.complexChangedTables["Song"]?.foreignKeyChanged).isTrue()
+ assertThat(diffResult.complexChangedTables["Song"]?.indexChanged).isFalse()
}
@Test
fun testComplexChangeInvolvingIndex() {
- try {
- SchemaDiffer(
- fromSchemaBundle = from.database,
- toSchemaBundle = toIndexAdded.database
- ).diffSchemas()
- fail("DiffException should have been thrown.")
- } catch (ex: DiffException) {
- assertThat(ex.errorMessage).isEqualTo(
- ProcessorErrors.tableWithComplexChangedSchemaFound("Song")
- )
- }
+ val diffResult = SchemaDiffer(
+ fromSchemaBundle = from.database,
+ toSchemaBundle = toIndexAdded.database
+ ).diffSchemas()
+
+ assertThat(diffResult.complexChangedTables.isNotEmpty())
+ assertThat(diffResult.complexChangedTables["Song"]?.foreignKeyChanged).isFalse()
+ assertThat(diffResult.complexChangedTables["Song"]?.indexChanged).isTrue()
}
@Test
@@ -81,7 +70,8 @@
fromSchemaBundle = from.database,
toSchemaBundle = toColumnAddedWithColumnInfoDefaultValue.database
).diffSchemas()
- assertThat(schemaDiffResult.addedColumns[0].fieldBundle.columnName).isEqualTo("artistId")
+ assertThat(schemaDiffResult.addedColumns["artistId"]?.fieldBundle?.columnName)
+ .isEqualTo("artistId")
}
@Test
@@ -125,21 +115,6 @@
}
@Test
- fun testColumnFieldBundleChanged() {
- try {
- SchemaDiffer(
- fromSchemaBundle = from.database,
- toSchemaBundle = toColumnAffinityChanged.database
- ).diffSchemas()
- fail("DiffException should have been thrown.")
- } catch (ex: DiffException) {
- assertThat(ex.errorMessage).isEqualTo(
- ProcessorErrors.columnWithChangedSchemaFound("length")
- )
- }
- }
-
- @Test
fun testColumnRemoved() {
try {
SchemaDiffer(
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
index 5379314..f1d962b 100644
--- a/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
@@ -19,9 +19,8 @@
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.runProcessorTest
-import androidx.room.migration.bundle.EntityBundle
import androidx.room.migration.bundle.FieldBundle
-import androidx.room.migration.bundle.PrimaryKeyBundle
+import androidx.room.util.SchemaDiffResult
import androidx.room.vo.AutoMigrationResult
import loadTestSource
import org.junit.Test
@@ -52,19 +51,27 @@
),
from = 1,
to = 2,
- addedColumns = listOf(
- AutoMigrationResult.AddedColumn(
- "Song",
- FieldBundle(
+ schemaDiff = SchemaDiffResult(
+ addedColumns = mapOf(
+ Pair(
"artistId",
- "artistId",
- "INTEGER",
- true,
- "0"
+ AutoMigrationResult.AddedColumn(
+ "Song",
+ FieldBundle(
+ "artistId",
+ "artistId",
+ "INTEGER",
+ true,
+ "0"
+ )
+ )
)
- )
+ ),
+ removedOrRenamedColumns = listOf(),
+ addedTables = listOf(),
+ complexChangedTables = mapOf(),
+ removedOrRenamedTables = listOf()
),
- addedTables = listOf()
)
AutoMigrationWriter(mock(XElement::class.java), autoMigrationResultWithNewAddedColumn)
.write(invocation.processingEnv)
@@ -102,19 +109,27 @@
),
from = 1,
to = 2,
- addedColumns = listOf(
- AutoMigrationResult.AddedColumn(
- "Song",
- FieldBundle(
+ schemaDiff = SchemaDiffResult(
+ addedColumns = mapOf(
+ Pair(
"artistId",
- "artistId",
- "INTEGER",
- false,
- ""
+ AutoMigrationResult.AddedColumn(
+ "Song",
+ FieldBundle(
+ "artistId",
+ "artistId",
+ "INTEGER",
+ false,
+ ""
+ )
+ )
)
- )
+ ),
+ removedOrRenamedColumns = listOf(),
+ addedTables = listOf(),
+ complexChangedTables = mapOf(),
+ removedOrRenamedTables = listOf()
),
- addedTables = listOf()
)
AutoMigrationWriter(mock(XElement::class.java), autoMigrationResultWithNewAddedColumn)
.write(invocation.processingEnv)
@@ -129,95 +144,4 @@
}
}
}
-
- @Test
- fun validAutoMigrationWithNewTableAdded() {
- val source = Source.java(
- "foo.bar.ValidAutoMigrationWithoutDefault",
- """
- package foo.bar;
- import androidx.room.migration.AutoMigrationCallback;
- import androidx.room.AutoMigration;
- import androidx.sqlite.db.SupportSQLiteDatabase;
- @AutoMigration(from=1, to=2)
- interface ValidAutoMigrationWithNewTableAdded extends AutoMigrationCallback {
- @Override
- void onPostMigrate(SupportSQLiteDatabase db);
- }
- """.trimIndent()
- )
-
- runProcessorTest(listOf(source)) { invocation ->
- val autoMigrationResultWithNewTableAdded = AutoMigrationResult(
- element = invocation.processingEnv.requireTypeElement(
- "foo.bar.ValidAutoMigrationWithNewTableAdded"
- ),
- from = 1,
- to = 2,
- addedColumns = listOf(
- AutoMigrationResult.AddedColumn(
- "Song",
- FieldBundle(
- "songId",
- "songId",
- "INTEGER",
- false,
- ""
- )
- )
- ),
- addedTables = listOf(
- AutoMigrationResult.AddedTable(
- EntityBundle(
- "Artist",
- "CREATE TABLE IF NOT EXISTS `Artist` (`artistId` INTEGER NOT NULL, " +
- "`name` TEXT NOT NULL, PRIMARY KEY(`artistId`))",
- listOf(
- FieldBundle(
- "artistId",
- "artistId",
- "INTEGER",
- true,
- "1"
- )
- ),
- PrimaryKeyBundle(true, listOf("artistId")),
- listOf(),
- listOf()
- ),
- ),
- AutoMigrationResult.AddedTable(
- EntityBundle(
- "Album",
- "CREATE TABLE IF NOT EXISTS `Album` (`albumId` INTEGER NOT NULL, " +
- "PRIMARY KEY(`albumId`))",
- listOf(
- FieldBundle(
- "albumId",
- "albumId",
- "INTEGER",
- true,
- "1"
- )
- ),
- PrimaryKeyBundle(true, listOf("albumId")),
- listOf(),
- listOf()
- )
- )
- )
- )
- AutoMigrationWriter(mock(XElement::class.java), autoMigrationResultWithNewTableAdded)
- .write(invocation.processingEnv)
-
- invocation.assertCompilationResult {
- generatedSource(
- loadTestSource(
- "autoMigrationWriter/output/ValidAutoMigrationWithNewTableAdded.java",
- "foo.bar.ValidAutoMigrationWithNewTableAdded_Impl"
- )
- )
- }
- }
- }
}
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/1.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/1.json
index 8f1c4a3..c02e4d1 100644
--- a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/1.json
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/1.json
@@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
- "identityHash": "83c0712e9973d534e0a9b882f1d7c4ce",
+ "identityHash": "4effe9c46eb525253fa7650696035fa5",
"entities": [
{
"tableName": "Entity1",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `value1` INTEGER NOT NULL DEFAULT 1, `value2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
@@ -21,18 +21,11 @@
"notNull": false
},
{
- "fieldPath": "value1",
- "columnName": "value1",
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
- },
- {
- "fieldPath": "value2",
- "columnName": "value2",
- "affinity": "INTEGER",
- "notNull": true,
- "defaultValue": "2"
}
],
"primaryKey": {
@@ -43,12 +36,443 @@
},
"indices": [],
"foreignKeys": []
+ },
+ {
+ "tableName": "Entity2",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity4",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity5",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity6",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity7",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity8",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity9",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity10",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`name`) REFERENCES `Entity14`(`name`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "Entity14",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "name"
+ ],
+ "referencedColumns": [
+ "name"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Entity11",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`name`) REFERENCES `Entity14`(`name`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "Entity14",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "name"
+ ],
+ "referencedColumns": [
+ "name"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Entity12",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity13",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_Entity13_name",
+ "unique": true,
+ "columnNames": [
+ "name"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_name` ON `${TABLE_NAME}` (`name`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity14",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_Entity14_name",
+ "unique": true,
+ "columnNames": [
+ "name"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity14_name` ON `${TABLE_NAME}` (`name`)"
+ }
+ ],
+ "foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '83c0712e9973d534e0a9b882f1d7c4ce')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4effe9c46eb525253fa7650696035fa5')"
]
}
}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/2.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/2.json
index da1b494..6ebbddb 100644
--- a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/2.json
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.AutoMigrationDb/2.json
@@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 2,
- "identityHash": "78c7261f6c50da0e69cf00d3277c1994",
+ "identityHash": "cb0906b793f21e53ed5ff0db35729db5",
"entities": [
{
"tableName": "Entity1",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `value1` INTEGER NOT NULL DEFAULT 1, `value2` INTEGER NOT NULL DEFAULT 2, `addedInV2` INTEGER NOT NULL DEFAULT 3, PRIMARY KEY(`id`))",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
@@ -21,25 +21,483 @@
"notNull": false
},
{
- "fieldPath": "value1",
- "columnName": "value1",
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity2",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
},
{
- "fieldPath": "value2",
- "columnName": "value2",
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
"affinity": "INTEGER",
"notNull": true,
- "defaultValue": "2"
+ "defaultValue": "1"
},
{
"fieldPath": "addedInV2",
"columnName": "addedInV2",
"affinity": "INTEGER",
"notNull": true,
- "defaultValue": "3"
+ "defaultValue": "2"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity3",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity4",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "2"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity5",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` TEXT DEFAULT '1', PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "TEXT",
+ "notNull": false,
+ "defaultValue": "'1'"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity6",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity7",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity8",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`name`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "name"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity9",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `Entity12`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "Entity12",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "id"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Entity10",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`addedInV1`) REFERENCES `Entity13`(`addedInV1`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_Entity10_addedInV1",
+ "unique": true,
+ "columnNames": [
+ "addedInV1"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity10_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "Entity13",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "addedInV1"
+ ],
+ "referencedColumns": [
+ "addedInV1"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "Entity11",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity12",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_Entity12_name",
+ "unique": true,
+ "columnNames": [
+ "name"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity12_name` ON `${TABLE_NAME}` (`name`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity13",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_Entity13_addedInV1",
+ "unique": true,
+ "columnNames": [
+ "addedInV1"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Entity14",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
}
],
"primaryKey": {
@@ -55,7 +513,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '78c7261f6c50da0e69cf00d3277c1994')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cb0906b793f21e53ed5ff0db35729db5')"
]
}
}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java
index 5e3c4aa..ce96535 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationDb.java
@@ -17,12 +17,14 @@
package androidx.room.integration.testapp.migration;
+import androidx.annotation.NonNull;
import androidx.room.AutoMigration;
import androidx.room.ColumnInfo;
import androidx.room.Dao;
import androidx.room.Database;
import androidx.room.Entity;
-import androidx.room.Insert;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.Query;
import androidx.room.RoomDatabase;
@@ -32,7 +34,22 @@
@Database(
version = AutoMigrationDb.LATEST_VERSION,
- entities = AutoMigrationDb.Entity1.class,
+ entities = {
+ AutoMigrationDb.Entity1.class,
+ AutoMigrationDb.Entity2.class,
+ AutoMigrationDb.Entity3.class,
+ AutoMigrationDb.Entity4.class,
+ AutoMigrationDb.Entity5.class,
+ AutoMigrationDb.Entity6.class,
+ AutoMigrationDb.Entity7.class,
+ AutoMigrationDb.Entity8.class,
+ AutoMigrationDb.Entity9.class,
+ AutoMigrationDb.Entity10.class,
+ AutoMigrationDb.Entity11.class,
+ AutoMigrationDb.Entity12.class,
+ AutoMigrationDb.Entity13.class,
+ AutoMigrationDb.Entity14.class
+ },
autoMigrations = AutoMigrationDb.SimpleAutoMigration.class,
exportSchema = true
)
@@ -46,21 +63,155 @@
public int id;
public String name;
@ColumnInfo(defaultValue = "1")
- public int value1;
+ public int addedInV1;
+ }
+
+ @Entity
+ static class Entity2 {
+ public static final String TABLE_NAME = "Entity2";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
@ColumnInfo(defaultValue = "2")
- public int value2;
- @ColumnInfo(defaultValue = "3")
public int addedInV2;
}
+ @Entity
+ static class Entity3 {
+ public static final String TABLE_NAME = "Entity3";
+ @PrimaryKey
+ public int id;
+ public String name;
+ }
+
+ @Entity
+ static class Entity4 {
+ public static final String TABLE_NAME = "Entity4";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "2")
+ public int addedInV1;
+ }
+
+ @Entity
+ static class Entity5 {
+ public static final String TABLE_NAME = "Entity5";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public String addedInV1;
+ }
+
+ @Entity
+ static class Entity6 {
+ public static final String TABLE_NAME = "Entity6";
+ @PrimaryKey
+ public int id;
+ public String name;
+ // @ColumnInfo(defaultValue = "1") - now nullable
+ public int addedInV1;
+ }
+
+ @Entity
+ static class Entity7 {
+ public static final String TABLE_NAME = "Entity7";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ @Entity
+ static class Entity8 {
+ public static final String TABLE_NAME = "Entity8";
+ public int id;
+ @PrimaryKey
+ @NonNull
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ @Entity(foreignKeys = {
+ @ForeignKey(entity = Entity12.class,
+ parentColumns = "id",
+ childColumns = "id",
+ deferred = true)})
+ static class Entity9 {
+ public static final String TABLE_NAME = "Entity9";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ @Entity(foreignKeys = {
+ @ForeignKey(entity = Entity13.class,
+ parentColumns = "addedInV1",
+ childColumns = "addedInV1",
+ deferred = true)},
+ indices = {@Index(value = "addedInV1", unique = true)})
+ static class Entity10 {
+ public static final String TABLE_NAME = "Entity10";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ @Entity
+ static class Entity11 {
+ public static final String TABLE_NAME = "Entity11";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ @Entity(indices = {@Index(value = "name", unique = true)})
+ static class Entity12 {
+ public static final String TABLE_NAME = "Entity12";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ @Entity(indices = {@Index(value = "addedInV1", unique = true)})
+ static class Entity13 {
+ public static final String TABLE_NAME = "Entity13";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ @Entity
+ static class Entity14 {
+ public static final String TABLE_NAME = "Entity14";
+ @PrimaryKey
+ public int id;
+ public String name;
+ }
+
@Dao
interface AutoMigrationDao {
- @Query("SELECT * from Entity1")
- List<Entity1> getAllEntity1s();
- @Insert
- void insert(AutoMigrationDb.Entity1... entity1);
+ @Query("SELECT * from Entity1 ORDER BY id ASC")
+ List<AutoMigrationDb.Entity1> getAllEntity1s();
}
@AutoMigration(from=1, to=2)
- interface SimpleAutoMigration extends AutoMigrationCallback {}
-}
+ interface SimpleAutoMigration extends AutoMigrationCallback {
+
+ }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
index cf53daf..5000a72 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
@@ -18,6 +18,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
import androidx.room.Room;
import androidx.room.testing.MigrationTestHelper;
@@ -42,12 +43,47 @@
@Rule
public MigrationTestHelper helper;
- // TODO: (b/181985265) Implement running AutoMigrations and validate.
public AutoMigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
AutoMigrationDb.class.getCanonicalName());
}
+ @Test
+ public void testBadAutoMigrationInput() throws IOException {
+ try (SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1)) {
+ db.execSQL("INSERT INTO Entity1 (id, name) VALUES (1, 'row1')");
+ AutoMigrationDb autoMigrationDbV2 = getLatestDb();
+ helper.runMigrationsAndValidate(
+ TEST_DB,
+ 2,
+ true,
+ autoMigrationDbV2.getAutoGeneratedMigration(2, 3)
+ );
+ fail();
+ } catch (IllegalArgumentException ex) {
+ assertThat(
+ ex.getMessage(),
+ is("No AutoMigrations between versions 'from = 2', 'to = "
+ + "3' have been provided. Annotate Database class with @AutoMigration(from = "
+ + "2, to = 3) to generate this AutoMigration.")
+ );
+ }
+ }
+
+ @Test
+ public void goFromV1ToV2() throws IOException {
+ try (SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1)) {
+ db.execSQL("INSERT INTO Entity1 (id, name) VALUES (1, 'row1')");
+ }
+ AutoMigrationDb autoMigrationDbV2 = getLatestDb();
+ helper.runMigrationsAndValidate(
+ TEST_DB,
+ 2,
+ true,
+ autoMigrationDbV2.getAutoGeneratedMigration(1, 2)
+ );
+ assertThat(autoMigrationDbV2.dao().getAllEntity1s().size(), is(1));
+ }
// Run this to create the very 1st version of the db.
public void createFirstVersion() throws IOException {
@@ -55,11 +91,7 @@
db.close();
}
- @Test
- public void addColumnToDatabaseWithOneTable() throws IOException {
- try (SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1)) {
- db.execSQL("INSERT INTO Entity1 (id, name) VALUES (1, 'row1')");
- }
+ public void createSecondVersion() {
AutoMigrationDb autoMigrationDbV2 = getLatestDb();
assertThat(autoMigrationDbV2.dao().getAllEntity1s().size(), is(1));
diff --git a/room/migration/api/restricted_current.txt b/room/migration/api/restricted_current.txt
index fe4aad7..436687e 100644
--- a/room/migration/api/restricted_current.txt
+++ b/room/migration/api/restricted_current.txt
@@ -78,10 +78,11 @@
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class IndexBundle {
ctor public IndexBundle(String!, boolean, java.util.List<java.lang.String!>!, String!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! create(String!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! create(String);
method public java.util.List<java.lang.String!>! getColumnNames();
+ method public String! getCreateSql(String!);
method public String! getName();
- method public boolean isSchemaEqual(androidx.room.migration.bundle.IndexBundle!);
+ method public boolean isSchemaEqual(androidx.room.migration.bundle.IndexBundle);
method public boolean isUnique();
field public static final String DEFAULT_PREFIX = "index_";
}
diff --git a/room/migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java b/room/migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
index 9a970d0..ae0693e 100644
--- a/room/migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
+++ b/room/migration/src/main/java/androidx/room/migration/bundle/IndexBundle.java
@@ -16,6 +16,7 @@
package androidx.room.migration.bundle;
+import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import com.google.gson.annotations.SerializedName;
@@ -61,15 +62,23 @@
}
/**
+ * @param tableName The table name.
+ * @return Create index SQL query that uses the given table name.
+ */
+ public String getCreateSql(String tableName) {
+ return BundleUtil.replaceTableName(mCreateSql, tableName);
+ }
+
+ /**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public String create(String tableName) {
+ public String create(@NonNull String tableName) {
return BundleUtil.replaceTableName(mCreateSql, tableName);
}
@Override
- public boolean isSchemaEqual(IndexBundle other) {
+ public boolean isSchemaEqual(@NonNull IndexBundle other) {
if (mUnique != other.mUnique) return false;
if (mName.startsWith(DEFAULT_PREFIX)) {
if (!other.mName.startsWith(DEFAULT_PREFIX)) {
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index 3411a26..f0d88eb 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -273,6 +273,39 @@
}
/**
+ * Returns a {@link Migration} of a database that have been generated using
+ * {@link AutoMigration} with the specific "from" and "to" version pair.
+ * <p>
+ * If a {@link Migration} with the given "from" and "to" versions cannot be found, this
+ * method will throw a {@link IllegalArgumentException}.
+ *
+ * <p>
+ * This API is intended for testing and all auto-migrations are added by default.
+ *
+ * @param from version of the original database schema to migrate from
+ * @param to version of the new database schema to migrate to
+ * @return migration instance of a generated autoMigration
+ *
+ * @hide
+ */
+ @NonNull
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public Migration getAutoGeneratedMigration(int from, int to) {
+ // TODO: (b/181985265) Support testing automigrations in MigrationTestHelper and remove
+ // this method
+ List<Migration> autoMigrations = getAutoMigrations();
+ for (Migration autoMigration : autoMigrations) {
+ if (autoMigration.startVersion == from && autoMigration.endVersion == to) {
+ return autoMigration;
+ }
+ }
+ throw new IllegalArgumentException("No AutoMigrations between versions 'from = " + from
+ + "', 'to = " + to + "' have been provided. Annotate Database class with "
+ + "@AutoMigration(from = " + from + ", to = " + to + ") to generate this "
+ + "AutoMigration.");
+ }
+
+ /**
* Unwraps (delegating) open helpers until it finds clazz, otherwise returns null.
*
* @param clazz the open helper type to search for