[go: nahoru, domu]

Implementing support for handling all schema changes except FTS Table related ones.

This CL implements functionality that is able to detect, categorize and generate a migrate() function for all complex and simple schema changes (except FTS Table related changes), including renames and deletes at the column and table level.

We now are able to get input from the user via the repeatable annotations: @RenameColumn, @RenameTable, @DeleteColumn, @DeleteTable. In the cases where the schema change classification fails in case of an ambiguity (e.g. a table is missing in the new version, no new tables have been discovered, and the table has not been annotated with @RenameTable or @DeleteTable), an error message is displayed to the user with sample code using these annotations.

In addition, we are now able to provide the autoMigrations to the database only by a list of @AutoMigration annotations. When the user opts to have a custom AutoMigrationCallback, they can include this interface in the annotation. It is important to note that to use the repeatable change annotations, the user must have a custom AutoMigrationCallback interface, as the repeatable annotations are only used on this callback. If the user does not provide a callback implementation, and potential column/table rename/deletes are encountered during the AutoMigration generation process, an error message will be displayed with sample code usage, notifying the user that they are required to define a custom callback and use the annotations to clarify the change.

Lastly, changes have been made in this CL to handle the case where the user includes an AutoMigration annotation with the same from and to version values as one of their custom Migration definitions. In this case, we will avoid overriding the pre-existing user defined Migration, and will not generate an autoMigration.

Test: The AutoMigrationTest.kt
Bug: 180395129, 183434667, 183007590, 182251019
Change-Id: Ic834c5d75753b5279e6d6c54745474258f816991
diff --git a/room/common/src/main/java/androidx/room/AutoMigration.java b/room/common/src/main/java/androidx/room/AutoMigration.java
index 4c9bc18..54361b0 100644
--- a/room/common/src/main/java/androidx/room/AutoMigration.java
+++ b/room/common/src/main/java/androidx/room/AutoMigration.java
@@ -46,4 +46,11 @@
      * @return Version number of the new database schema.
      */
     int to();
+
+    /**
+     * User implemented custom AutoMigration callback interface.
+     *
+     * @return Null if the user has not implemented a callback
+     */
+    Class<?> callback() default Object.class;
 }
diff --git a/room/common/src/main/java/androidx/room/Database.java b/room/common/src/main/java/androidx/room/Database.java
index 2659bc8..e4f9366 100644
--- a/room/common/src/main/java/androidx/room/Database.java
+++ b/room/common/src/main/java/androidx/room/Database.java
@@ -63,7 +63,7 @@
  */
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
-public @interface Database {
+public @interface  Database {
     /**
      * The list of entities included in the database. Each entity turns into a table in the
      * database.
@@ -108,9 +108,9 @@
     /**
      * List of AutoMigrations that can be performed on this Database.
      *
-     * @return List of AutoMigrations.
+     * @return List of AutoMigration annotations.
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    Class<?>[] autoMigrations() default {};
+    AutoMigration[] autoMigrations() default {};
 }
diff --git a/room/common/src/main/java/androidx/room/DeleteColumn.java b/room/common/src/main/java/androidx/room/DeleteColumn.java
new file mode 100644
index 0000000..854b969
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/DeleteColumn.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Repeatable annotation to be used by the user in specifying deleted columns in the new versions
+ * of one database.
+ *
+ * @hide
+ */
+@Repeatable(DeleteColumn.Entries.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public @interface DeleteColumn {
+    /**
+     * Name of the table in the previous version of the database the column was deleted from.
+     *
+     * @return Name of the table
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String tableName();
+
+    /**
+     * Name of the column deleted in the new version of the database.
+     *
+     * @return Name of the column.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String deletedColumnName();
+
+    @Target(ElementType.TYPE)
+    @Retention(RetentionPolicy.CLASS)
+    @interface Entries {
+        DeleteColumn[] value();
+    }
+}
diff --git a/room/common/src/main/java/androidx/room/DeleteTable.java b/room/common/src/main/java/androidx/room/DeleteTable.java
new file mode 100644
index 0000000..a8dd32f
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/DeleteTable.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Repeatable annotation to be used by the user in specifying deleted tables between the old and
+ * new versions of one database.
+ *
+ * @hide
+ */
+@Repeatable(DeleteTable.Entries.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public @interface DeleteTable {
+    /**
+     * Name of the table in the previous version of the database to be deleted.
+     *
+     * @return Name of the table.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String deletedTableName();
+
+    @Target(ElementType.TYPE)
+    @Retention(RetentionPolicy.CLASS)
+    @interface Entries {
+        DeleteTable[] value();
+    }
+}
diff --git a/room/common/src/main/java/androidx/room/RenameColumn.java b/room/common/src/main/java/androidx/room/RenameColumn.java
new file mode 100644
index 0000000..8663c6f
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/RenameColumn.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Repeatable annotation to be used by the user in specifying renamed columns between the old and
+ * new versions of one database.
+ *
+ * @hide
+ */
+@Repeatable(RenameColumn.Entries.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public @interface RenameColumn {
+    /**
+     * Name of the table the renamed column is found in.
+     *
+     * @return Name of the table
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String tableName();
+
+    /**
+     * Original name of the column to be renamed from.
+     *
+     * @return Name of the column in the previous version of the database.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String originalColumnName();
+
+    /**
+     * New name of the column to be renamed to.
+     *
+     * @return Name of the column in the new version of the database.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String newColumnName();
+
+    @Target(ElementType.TYPE)
+    @Retention(RetentionPolicy.CLASS)
+    @interface Entries {
+        RenameColumn[] value();
+    }
+}
+
diff --git a/room/common/src/main/java/androidx/room/RenameTable.java b/room/common/src/main/java/androidx/room/RenameTable.java
new file mode 100644
index 0000000..4599033
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/RenameTable.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Repeatable annotation to be used by the user in specifying renamed tables between the old and
+ * new versions of one database.
+ *
+ * @hide
+ */
+@Repeatable(RenameTable.Entries.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public @interface RenameTable {
+    /**
+     * Name of the table in the previous version of the database.
+     *
+     * @return Original name of the table.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String originalTableName();
+
+    /**
+     * Name of the table in the new version of the database.
+     *
+     * @return New name of the table.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String newTableName();
+
+    @Target(ElementType.TYPE)
+    @Retention(RetentionPolicy.CLASS)
+    @interface Entries {
+        RenameTable[] value();
+    }
+}
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 1a8aa08..a6ddb62 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
@@ -16,22 +16,30 @@
 
 package androidx.room.processor
 
-import androidx.room.AutoMigration
+import androidx.room.DeleteColumn
+import androidx.room.DeleteTable
+import androidx.room.RenameColumn
+import androidx.room.RenameTable
+import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.ext.RoomTypeNames
 import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.migration.bundle.SchemaBundle.deserialize
+import androidx.room.processor.ProcessorErrors.AUTOMIGRATION_CALLBACK_MUST_BE_INTERFACE
+import androidx.room.processor.ProcessorErrors.autoMigrationElementMustExtendCallback
+import androidx.room.processor.ProcessorErrors.autoMigrationToVersionMustBeGreaterThanFrom
 import androidx.room.util.DiffException
 import androidx.room.util.SchemaDiffer
 import androidx.room.vo.AutoMigrationResult
 import java.io.File
 
 // TODO: (b/183435544) Support downgrades in AutoMigrations.
-// TODO: (b/183007590) Use the callback in the AutoMigration annotation while end-to-end
-//  testing, when column/table rename/deletes are supported
 class AutoMigrationProcessor(
-    val context: Context,
     val element: XTypeElement,
+    val context: Context,
+    val from: Int,
+    val to: Int,
+    val callback: XType,
     val latestDbSchema: DatabaseBundle
 ) {
     /**
@@ -41,41 +49,36 @@
      * @return the AutoMigrationResult containing the schema changes detected
      */
     fun process(): AutoMigrationResult? {
-        if (!element.isInterface()) {
-            context.logger.e(
-                ProcessorErrors.AUTOMIGRATION_ANNOTATED_TYPE_ELEMENT_MUST_BE_INTERFACE,
-                element
-            )
-            return null
-        }
+        val callbackElement = callback.typeElement
+        if (!callback.isTypeOf(Any::class)) {
+            if (callbackElement == null) {
+                context.logger.e(element, AUTOMIGRATION_CALLBACK_MUST_BE_INTERFACE)
+                return null
+            }
 
-        if (!context.processingEnv
-            .requireType(RoomTypeNames.AUTO_MIGRATION_CALLBACK)
-            .isAssignableFrom(element.type)
-        ) {
-            context.logger.e(
-                ProcessorErrors.AUTOMIGRATION_ELEMENT_MUST_IMPLEMENT_AUTOMIGRATION_CALLBACK,
-                element
-            )
-            return null
-        }
+            if (!callbackElement.isInterface()) {
+                context.logger.e(
+                    callbackElement,
+                    AUTOMIGRATION_CALLBACK_MUST_BE_INTERFACE
+                )
+                return null
+            }
 
-        val annotationBox = element.getAnnotation(AutoMigration::class)
-        if (annotationBox == null) {
-            context.logger.e(
-                element,
-                ProcessorErrors.AUTOMIGRATION_ANNOTATION_MISSING
-            )
-            return null
+            val extendsMigrationCallback =
+                context.processingEnv.requireType(RoomTypeNames.AUTO_MIGRATION_CALLBACK)
+                    .isAssignableFrom(callback)
+            if (!extendsMigrationCallback) {
+                context.logger.e(
+                    callbackElement,
+                    autoMigrationElementMustExtendCallback(callbackElement.className.simpleName())
+                )
+                return null
+            }
         }
 
-        val from = annotationBox.value.from
-        val to = annotationBox.value.to
-
         if (to <= from) {
             context.logger.e(
-                ProcessorErrors.autoMigrationToVersionMustBeGreaterThanFrom(to, from),
-                element
+                autoMigrationToVersionMustBeGreaterThanFrom(to, from)
             )
             return null
         }
@@ -94,10 +97,52 @@
             }
         }
 
+        val callbackClassName = callbackElement?.className?.simpleName()
+        val deleteColumnEntries = callbackElement?.let { element ->
+            element.getAnnotations(DeleteColumn::class).map {
+                AutoMigrationResult.DeletedColumn(
+                    tableName = it.value.tableName,
+                    columnName = it.value.deletedColumnName
+                )
+            }
+        } ?: emptyList()
+
+        val deleteTableEntries = callbackElement?.let { element ->
+            element.getAnnotations(DeleteTable::class).map {
+                AutoMigrationResult.DeletedTable(
+                    deletedTableName = it.value.deletedTableName
+                )
+            }
+        } ?: emptyList()
+
+        val renameTableEntries = callbackElement?.let { element ->
+            element.getAnnotations(RenameTable::class).map {
+                AutoMigrationResult.RenamedTable(
+                    originalTableName = it.value.originalTableName,
+                    newTableName = it.value.newTableName
+                )
+            }
+        } ?: emptyList()
+
+        val renameColumnEntries = callbackElement?.let { element ->
+            element.getAnnotations(RenameColumn::class).map {
+                AutoMigrationResult.RenamedColumn(
+                    tableName = it.value.tableName,
+                    originalColumnName = it.value.originalColumnName,
+                    newColumnName = it.value.newColumnName
+                )
+            }
+        } ?: emptyList()
+
         val schemaDiff = try {
             SchemaDiffer(
                 fromSchemaBundle = fromSchemaBundle,
-                toSchemaBundle = toSchemaBundle
+                toSchemaBundle = toSchemaBundle,
+                className = callbackClassName,
+                deleteColumnEntries = deleteColumnEntries,
+                deleteTableEntries = deleteTableEntries,
+                renameTableEntries = renameTableEntries,
+                renameColumnEntries = renameColumnEntries
             ).diffSchemas()
         } catch (ex: DiffException) {
             context.logger.e(ex.errorMessage)
@@ -116,13 +161,13 @@
     private fun getValidatedSchemaFile(version: Int): File? {
         val schemaFile = File(
             context.schemaOutFolder,
-            "${element.className.enclosingClassName()}/$version.json"
+            "${element.className.canonicalName()}/$version.json"
         )
         if (!schemaFile.exists()) {
             context.logger.e(
                 ProcessorErrors.autoMigrationSchemasNotFound(
                     context.schemaOutFolder.toString(),
-                    "${element.className.enclosingClassName()}/$version.json"
+                    "${element.className.canonicalName()}/$version.json"
                 ),
                 element
             )
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index 35ca51e..76dcc58 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.processor
 
+import androidx.room.AutoMigration
 import androidx.room.SkipQueryVerification
 import androidx.room.compiler.processing.XAnnotationBox
 import androidx.room.compiler.processing.XElement
@@ -126,7 +127,8 @@
         latestDbSchema: DatabaseBundle
     ): List<AutoMigrationResult> {
         val dbAnnotation = element.getAnnotation(androidx.room.Database::class)!!
-        val autoMigrationList = dbAnnotation.getAsTypeList("autoMigrations")
+        val autoMigrationList = dbAnnotation
+            .getAsAnnotationBoxArray<AutoMigration>("autoMigrations")
         context.checker.check(
             autoMigrationList.isEmpty() || dbAnnotation.value.exportSchema,
             element,
@@ -134,22 +136,15 @@
         )
 
         return autoMigrationList.mapNotNull {
-            val typeElement = it.typeElement
-            if (typeElement == null) {
-                context.logger.e(
-                    element,
-                    ProcessorErrors.invalidAutoMigrationTypeInDatabaseAnnotation(
-                        it.typeName
-                    )
-                )
-                null
-            } else {
-                AutoMigrationProcessor(
-                    context = context,
-                    element = typeElement,
-                    latestDbSchema = latestDbSchema
-                ).process()
-            }
+            val autoMigration = it.value
+            AutoMigrationProcessor(
+                element = element,
+                context = context,
+                from = autoMigration.from,
+                to = autoMigration.to,
+                callback = it.getAsType("callback")!!,
+                latestDbSchema = latestDbSchema
+            ).process()
         }
     }
 
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 a426677..f6a8799 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -793,9 +793,9 @@
             "interface."
     }
 
-    fun invalidAutoMigrationTypeInDatabaseAnnotation(typeName: TypeName): String {
-        return "Invalid AutoMigration type: $typeName. An automigration in the database must be a" +
-            " class."
+    fun invalidAutoMigrationTypeInDatabaseAnnotation(): String {
+        return "Invalid AutoMigration type: An automigration in the database must be " +
+            "an @AutoMigration annotation."
     }
 
     val EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE = "The type of an Embedded field must be a " +
@@ -809,12 +809,13 @@
         return "Invalid query argument: $typeName. It must be a class or an interface."
     }
 
-    val AUTOMIGRATION_ANNOTATED_TYPE_ELEMENT_MUST_BE_INTERFACE = "The @AutoMigration annotated " +
+    val AUTOMIGRATION_CALLBACK_MUST_BE_INTERFACE = "The @AutoMigration callback " +
         "type must be an interface."
-    val AUTOMIGRATION_ANNOTATION_MISSING = "The @AutoMigration annotation has not been found. " +
-        "Cannot generate auto migrations."
-    val AUTOMIGRATION_ELEMENT_MUST_IMPLEMENT_AUTOMIGRATION_CALLBACK = "AutoMigration element must" +
-        " implement the AutoMigrationCallback interface."
+
+    fun autoMigrationElementMustExtendCallback(callback: String): String {
+        return "The AutoMigration callback " +
+            "$callback must extend the AutoMigrationCallback interface."
+    }
 
     // TODO: (b/180389433) If the files don't exist the getSchemaFile() method should return
     //  null and before calling process
@@ -838,40 +839,109 @@
             "@ColumnInfo."
     }
 
-    fun nullabilityOfColumnChangedNotNullColumnMustHaveDefaultValue(columnName: String): String {
-        return "The nullability of the " +
-            "column '$columnName' " +
-            "has been changed from NULL to NOT NULL with no default value specified. Please " +
-            "specify the default value using @ColumnInfo."
-    }
-
     fun columnWithChangedSchemaFound(columnName: String): String {
         return "Encountered column '$columnName' with an unsupported schema change at the column " +
             "level (e.g. affinity change). These changes are not yet " +
             "supported by AutoMigration."
     }
 
-    fun removedOrRenamedColumnFound(columnName: String): String {
-        return "Column '$columnName' has been either removed or " +
-            "renamed. This change is not currently supported by AutoMigration."
+    fun deletedOrRenamedColumnFound(
+        className: String?,
+        columnName: String,
+        tableName: String
+    ): String {
+        return if (className != null) {
+            """
+            AutoMigration Failure in ‘$className’: Column ‘$columnName’ in table ‘$tableName’ has
+            been either removed or renamed. Please annotate ‘$className’ with the @RenameColumn
+            or @RemoveColumn annotation to specify the change to be performed:
+            1) RENAME: @RenameColumn.Entries(
+                    @RenameColumn(
+                        tableName = "$tableName",
+                        originalColumnName = "$columnName",
+                        newColumnName = <NEW_COLUMN_NAME>
+                    )
+                )
+            2) DELETE: @DeleteColumn.Entries(
+                    @DeleteColumn=(
+                        tableName = "$tableName",
+                        deletedColumnName = "$columnName"
+                    )
+                )
+            """
+        } else {
+            """
+            AutoMigration Failure: Please declare an interface extending 'AutoMigrationCallback',
+            and annotate with the @RenameColumn or @RemoveColumn annotation to specify the
+            change to be performed:
+            1) RENAME: @RenameColumn.Entries(
+                    @RenameColumn(
+                        tableName = "$tableName",
+                        originalColumnName = "$columnName",
+                        newColumnName = <NEW_COLUMN_NAME>
+                    )
+                )
+            2) DELETE: @DeleteColumn.Entries(
+                    @DeleteColumn=(
+                        tableName = "$tableName",
+                        deletedColumnName = "$columnName"
+                    )
+                )
+            """
+        }
     }
 
-    fun tableWithComplexChangedSchemaFound(tableName: String): String {
-        return "Encountered table '$tableName' with an unsupported schema change at the table " +
-            "level (e.g. primary key, foreign key or index change). These changes are not yet " +
-            "supported by AutoMigration."
+    fun deletedOrRenamedTableFound(
+        className: String?,
+        tableName: String
+    ): String {
+        return if (className != null) {
+            """
+            AutoMigration Failure in '$className': Table '$tableName' has been either removed or
+            renamed. Please annotate '$className' with the @RenameTable or @RemoveTable
+            annotation to specify the change to be performed:
+            1) RENAME: @RenameTable.Entries(
+                    @RenameTable(originalTableName = "$tableName", newTableName = <NEW_TABLE_NAME>))
+            2) DELETE: @DeleteTable.Entries(@DeleteTable(deletedTableName = "$tableName"))
+            """
+        } else {
+            """
+            AutoMigration Failure: Please declare an interface extending 'AutoMigrationCallback',
+            and annotate with the @RenameTable or @RemoveTable
+            annotation to specify the change to be performed:
+            1) RENAME: @RenameTable.Entries(
+                    @RenameTable(originalTableName = "$tableName", newTableName = <NEW_TABLE_NAME>))
+            2) DELETE: @DeleteTable.Entries(@DeleteTable(deletedTableName = "$tableName"))
+            """
+        }
     }
 
-    fun removedOrRenamedTableFound(tableName: String): String {
-        return "Table '$tableName' has been either removed or " +
-            "renamed. This change is not currently supported by AutoMigration."
+    fun tableRenameError(
+        className: String,
+        originalTableName: String,
+        newTableName: String
+    ): String {
+        return "AutoMigration Failure in '$className': The table renamed from " +
+            "'$originalTableName' to '$newTableName' is " +
+            "not found in the new version of the database."
+    }
+
+    fun conflictingRenameTableAnnotationsFound(annotations: String): String {
+        return "Conflicting @RenameTable annotations found: [$annotations]"
+    }
+    fun conflictingRenameColumnAnnotationsFound(annotations: String): String {
+        return "Conflicting @RenameColumn annotations found: [$annotations]"
     }
 
     val AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF = "Cannot create auto-migrations when export " +
         "schema is OFF."
 
-    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."
+    fun tableWithConflictingPrefixFound(tableName: String): String {
+        return "The new version of the schema contains '$tableName' a table name" +
+            " with the prefix '_new_', which will cause conflicts for auto migrations. Please use" +
+            " a different name."
     }
+
+    val FTS_TABLE_NOT_CURRENTLY_SUPPORTED = "Schemas involving FTS tables are not currently " +
+        "supported."
 }
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 2208483..7a2a4d8 100644
--- a/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/util/SchemaDiffer.kt
@@ -18,40 +18,81 @@
 
 import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.migration.bundle.EntityBundle
+import androidx.room.migration.bundle.FieldBundle
 import androidx.room.migration.bundle.ForeignKeyBundle
+import androidx.room.migration.bundle.FtsEntityBundle
 import androidx.room.migration.bundle.IndexBundle
+import androidx.room.processor.ProcessorErrors.FTS_TABLE_NOT_CURRENTLY_SUPPORTED
+import androidx.room.processor.ProcessorErrors.deletedOrRenamedTableFound
+import androidx.room.processor.ProcessorErrors.tableRenameError
+import androidx.room.processor.ProcessorErrors.conflictingRenameColumnAnnotationsFound
+import androidx.room.processor.ProcessorErrors.conflictingRenameTableAnnotationsFound
 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.processor.ProcessorErrors.deletedOrRenamedColumnFound
+import androidx.room.processor.ProcessorErrors.tableWithConflictingPrefixFound
 import androidx.room.vo.AutoMigrationResult
 
 /**
  * This exception should be thrown to abandon processing an @AutoMigration.
+ *
+ * @param errorMessage Error message to be thrown with the exception
+ * @return RuntimeException with the provided error message
  */
 class DiffException(val errorMessage: String) : RuntimeException(errorMessage)
 
 /**
- * Contains the added, changed and removed columns detected.
+ * Contains the changes detected between the two schema versions provided.
  */
 data class SchemaDiffResult(
     val addedColumns: Map<String, AutoMigrationResult.AddedColumn>,
-    val removedOrRenamedColumns: List<AutoMigrationResult.RemovedOrRenamedColumn>,
-    val addedTables: List<AutoMigrationResult.AddedTable>,
+    val deletedColumns: List<AutoMigrationResult.DeletedColumn>,
+    val addedTables: Set<AutoMigrationResult.AddedTable>,
+    val renamedTables: Map<String, String>,
     val complexChangedTables: Map<String, AutoMigrationResult.ComplexChangedTable>,
-    val removedOrRenamedTables: List<AutoMigrationResult.RemovedOrRenamedTable>
+    val deletedTables: List<String>
 )
 
 /**
- * Receives the two bundles, diffs and returns a @SchemaDiffResult.
+ * Receives the two bundles, detects all changes between the two versions and returns a
+ * @SchemaDiffResult.
  *
- * Throws an @AutoMigrationException with a detailed error message when an AutoMigration cannot
+ * Throws an @DiffException with a detailed error message when an AutoMigration cannot
  * be generated.
+ *
+ * @param fromSchemaBundle Original database schema to migrate from
+ * @param toSchemaBundle New database schema to migrate to
+ * @param className Name of the user implemented AutoMigrationCallback interface, if available
+ * @param renameColumnEntries List of repeatable annotations specifying column renames
+ * @param deleteColumnEntries List of repeatable annotations specifying column deletes
+ * @param renameTableEntries List of repeatable annotations specifying table renames
+ * @param deleteTableEntries List of repeatable annotations specifying table deletes
  */
+// TODO: (b/181777611) Handle FTS tables
 class SchemaDiffer(
-    val fromSchemaBundle: DatabaseBundle,
-    val toSchemaBundle: DatabaseBundle
+    private val fromSchemaBundle: DatabaseBundle,
+    private val toSchemaBundle: DatabaseBundle,
+    private val className: String?,
+    private val renameColumnEntries: List<AutoMigrationResult.RenamedColumn>,
+    private val deleteColumnEntries: List<AutoMigrationResult.DeletedColumn>,
+    private val renameTableEntries: List<AutoMigrationResult.RenamedTable>,
+    private val deleteTableEntries: List<AutoMigrationResult.DeletedTable>
 ) {
+    private val potentiallyDeletedTables = mutableSetOf<String>()
+
+    private val addedTables = mutableSetOf<AutoMigrationResult.AddedTable>()
+    // Any table that has been renamed, but also does not contain any complex changes.
+    private val renamedTables = mutableMapOf<String, String>()
+
+    // Map of tables with complex changes, keyed by the table name, note that if the table is
+    // renamed, the original table name is used as key.
+    private val complexChangedTables =
+        mutableMapOf<String, AutoMigrationResult.ComplexChangedTable>()
+    private val deletedTables = deleteTableEntries.map { it.deletedTableName }.toSet()
+
+    // Map of columns that have been added in the database, keyed by the column name, note that
+    // the table these columns have been added to will not contain any complex schema changes.
+    private val addedColumns = mutableMapOf<String, AutoMigrationResult.AddedColumn>()
+    private val deletedColumns = deleteColumnEntries
 
     /**
      * Compares the two versions of the database based on the schemas provided, and detects
@@ -60,163 +101,258 @@
      * @return the AutoMigrationResult containing the schema changes detected
      */
     fun diffSchemas(): SchemaDiffResult {
-        val addedTables = mutableListOf<AutoMigrationResult.AddedTable>()
-        val complexChangedTables = mutableMapOf<String, AutoMigrationResult.ComplexChangedTable>()
-        val removedOrRenamedTables = mutableListOf<AutoMigrationResult.RemovedOrRenamedTable>()
-
-        val addedColumns = mutableMapOf<String, AutoMigrationResult.AddedColumn>()
-        val removedOrRenamedColumns = mutableListOf<AutoMigrationResult.RemovedOrRenamedColumn>()
+        val processedTablesAndColumnsInNewVersion = mutableMapOf<String, List<String>>()
 
         // 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) {
-                // 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[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) &&
-                        !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) {
-                        // 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
-                            )
-                        )
-                    }
-                }
+        // deleted columns/tables
+        fromSchemaBundle.entitiesByTableName.values.forEach { fromTable ->
+            val toTable = detectTableLevelChanges(fromTable)
+            if (fromTable is FtsEntityBundle) {
+                diffError(FTS_TABLE_NOT_CURRENTLY_SUPPORTED)
             }
-        }
-        // Check going from the new version of the schema to the original version for added
-        // tables/columns. Skip the columns with the same name as the previous loop would have
-        // processed them already.
-        toSchemaBundle.entitiesByTableName.forEach { toTable ->
-            val fromTable = fromSchemaBundle.entitiesByTableName[toTable.key]
-            if (fromTable == null) {
-                addedTables.add(AutoMigrationResult.AddedTable(toTable.value))
-            } else {
+
+            // Check for column related changes. Since we require toTable to not be null, any
+            // deleted tables will be skipped here.
+            if (toTable != null) {
                 val fromColumns = fromTable.fieldsByColumnName
-                val toColumns = toTable.value.fieldsByColumnName
-                toColumns.entries.forEach { toColumn ->
-                    val match = fromColumns[toColumn.key]
-                    if (match == null) {
-                        if (toColumn.value.isNonNull && toColumn.value.defaultValue == null) {
-                            diffError(
-                                newNotNullColumnMustHaveDefaultValue(toColumn.key)
-                            )
-                        }
-                        // 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
-                                )
-                        }
-                    }
+                val processedColumnsInNewVersion = fromColumns.values.mapNotNull { fromColumn ->
+                    detectColumnLevelChanges(
+                        fromTable,
+                        toTable,
+                        fromColumn
+                    )
                 }
+                processedTablesAndColumnsInNewVersion[toTable.tableName] =
+                    processedColumnsInNewVersion
             }
         }
 
-        if (removedOrRenamedColumns.isNotEmpty()) {
-            removedOrRenamedColumns.forEach { removedColumn ->
-                diffError(
-                    removedOrRenamedColumnFound(
-                        removedColumn.fieldBundle.columnName
-                    )
+        // Check going from the new version of the schema to the original version for added
+        // tables/columns. Skip the columns that have been processed already.
+        toSchemaBundle.entitiesByTableName.forEach { toTable ->
+            processAddedTableAndColumns(toTable.value, processedTablesAndColumnsInNewVersion)
+        }
+
+        potentiallyDeletedTables.forEach { tableName ->
+            diffError(
+                deletedOrRenamedTableFound(
+                    className = className,
+                    tableName = tableName
                 )
-            }
+            )
         }
 
-        if (removedOrRenamedTables.isNotEmpty()) {
-            removedOrRenamedTables.forEach { removedTable ->
-                diffError(
-                    removedOrRenamedTableFound(
-                        removedTable.entityBundle.tableName
-                    )
-                )
-            }
-        }
-
+        processDeletedColumns()
         return SchemaDiffResult(
             addedColumns = addedColumns,
-            removedOrRenamedColumns = removedOrRenamedColumns,
+            deletedColumns = deletedColumns,
             addedTables = addedTables,
+            renamedTables = renamedTables,
             complexChangedTables = complexChangedTables,
-            removedOrRenamedTables = removedOrRenamedTables
+            deletedTables = deletedTables.toList()
         )
     }
 
     /**
-     * Check for complex schema changes at a Table level and returns a ComplexTableChange
-     * including information on which table changes were found on, and whether foreign key or
-     * index related changes have occurred.
+     * Detects any changes at the table-level, independent of any changes that may be present at
+     * the column-level (e.g. column add/rename/delete).
      *
-     * @return null if complex schema change has not been found
+     * @param fromTable The original version of the table
+     * @return The EntityBundle of the table in the new version of the database. If the
+     * table was renamed, this will be reflected in the return value. If the table was removed, a
+     * null object will be returned.
      */
-    // TODO: (b/181777611) Handle FTS tables
-    private fun tableContainsComplexChanges(
-        fromTable: EntityBundle,
-        toTable: EntityBundle
-    ): AutoMigrationResult.ComplexChangedTable? {
-        val foreignKeyChanged = !isForeignKeyBundlesListEqual(
-            fromTable.foreignKeys,
-            toTable.foreignKeys
-        )
-        val indexChanged = !isIndexBundlesListEqual(fromTable.indices, toTable.indices)
-        val primaryKeyChanged = !fromTable.primaryKey.isSchemaEqual(toTable.primaryKey)
-
-        if (primaryKeyChanged || foreignKeyChanged || indexChanged) {
-            if (toSchemaBundle.entitiesByTableName.containsKey(toTable.newTableName)) {
-                diffError(tableWithNewTablePrefixFound(toTable.newTableName))
+    private fun detectTableLevelChanges(
+        fromTable: EntityBundle
+    ): EntityBundle? {
+        // Check if the table was renamed. If so, check for other complex changes that could
+        // be found on the table level. Save the end result to the complex changed tables map.
+        val renamedTable = isTableRenamed(fromTable.tableName)
+        if (renamedTable != null) {
+            val toTable = toSchemaBundle.entitiesByTableName[renamedTable.newTableName]
+            if (toTable != null) {
+                val isComplexChangedTable = tableContainsComplexChanges(
+                    fromTable,
+                    toTable
+                )
+                if (isComplexChangedTable) {
+                    if (toSchemaBundle.entitiesByTableName.containsKey(toTable.newTableName)) {
+                        diffError(tableWithConflictingPrefixFound(toTable.newTableName))
+                    }
+                    renamedTables.remove(renamedTable.originalTableName)
+                    complexChangedTables[renamedTable.originalTableName] =
+                        AutoMigrationResult.ComplexChangedTable(
+                            tableName = toTable.tableName,
+                            tableNameWithNewPrefix = toTable.newTableName,
+                            oldVersionEntityBundle = fromTable,
+                            newVersionEntityBundle = toTable,
+                            renamedColumnsMap = mutableMapOf()
+                        )
+                } else {
+                    renamedTables[fromTable.tableName] = toTable.tableName
+                }
+            } else {
+                // The table we renamed TO does not exist in the new version
+                diffError(
+                    tableRenameError(
+                        className!!,
+                        renamedTable.originalTableName,
+                        renamedTable.newTableName
+                    )
+                )
             }
-            return AutoMigrationResult.ComplexChangedTable(
-                tableName = toTable.tableName,
-                newTableName = toTable.newTableName,
-                oldVersionEntityBundle = fromTable,
-                newVersionEntityBundle = toTable,
-                foreignKeyChanged = foreignKeyChanged,
-                indexChanged = indexChanged
+            return toTable
+        }
+        val toTable = toSchemaBundle.entitiesByTableName[fromTable.tableName]
+        val isDeletedTable = deletedTables.contains(fromTable.tableName)
+        if (toTable != null) {
+            if (isDeletedTable) {
+                diffError(
+                    deletedOrRenamedTableFound(className, toTable.tableName)
+                )
+            }
+
+            // Check if this table exists in both versions of the schema, hence is not renamed or
+            // deleted, but contains other complex changes (index/primary key/foreign key change).
+            val isComplexChangedTable = tableContainsComplexChanges(
+                fromTable = fromTable,
+                toTable = toTable
+            )
+            if (isComplexChangedTable) {
+                complexChangedTables[fromTable.tableName] =
+                    AutoMigrationResult.ComplexChangedTable(
+                        tableName = toTable.tableName,
+                        tableNameWithNewPrefix = toTable.newTableName,
+                        oldVersionEntityBundle = fromTable,
+                        newVersionEntityBundle = toTable,
+                        renamedColumnsMap = mutableMapOf()
+                    )
+            }
+            return toTable
+        }
+        if (!isDeletedTable) {
+            potentiallyDeletedTables.add(fromTable.tableName)
+        }
+        return null
+    }
+
+    /**
+     * Detects any changes at the column-level.
+     *
+     * @param fromTable The original version of the table
+     * @param toTable The new version of the table
+     * @param fromColumn The original version of the column
+     * @return The name of the column in the new version of the database. Will return a null
+     * value if the column was deleted.
+     */
+    private fun detectColumnLevelChanges(
+        fromTable: EntityBundle,
+        toTable: EntityBundle,
+        fromColumn: FieldBundle,
+    ): String? {
+        // Check if this column was renamed. If so, no need to check further, we can mark this
+        // table as a complex change and include the renamed column.
+        val renamedToColumn = isColumnRenamed(fromColumn.columnName, fromTable.tableName)
+        if (renamedToColumn != null) {
+            val renamedColumnsMap = mutableMapOf(
+                renamedToColumn.newColumnName to fromColumn.columnName
+            )
+            // Make sure there are no conflicts in the new version of the table with the
+            // temporary new table name
+            // TODO: (b/183975119) Use another prefix automatically in these cases
+            if (toSchemaBundle.entitiesByTableName.containsKey(toTable.newTableName)) {
+                diffError(tableWithConflictingPrefixFound(toTable.newTableName))
+            }
+            renamedTables.remove(fromTable.tableName)
+            complexChangedTables[fromTable.tableName] =
+                AutoMigrationResult.ComplexChangedTable(
+                    tableName = fromTable.tableName,
+                    tableNameWithNewPrefix = toTable.newTableName,
+                    oldVersionEntityBundle = fromTable,
+                    newVersionEntityBundle = toTable,
+                    renamedColumnsMap = renamedColumnsMap
+                )
+            return renamedToColumn.newColumnName
+        }
+        // The column was not renamed. So we check if the column was deleted, and
+        // if not, we check for column level complex changes.
+        val match = toTable.fieldsByColumnName[fromColumn.columnName]
+        if (match != null) {
+            val columnChanged = !match.isSchemaEqual(fromColumn)
+            if (columnChanged && !complexChangedTables.containsKey(fromTable.tableName)) {
+                // Make sure there are no conflicts in the new version of the table with the
+                // temporary new table name
+                // TODO: (b/183975119) Use another prefix automatically in these cases
+                if (toSchemaBundle.entitiesByTableName.containsKey(toTable.newTableName)) {
+                    diffError(tableWithConflictingPrefixFound(toTable.newTableName))
+                }
+                renamedTables.remove(fromTable.tableName)
+                complexChangedTables[fromTable.tableName] =
+                    AutoMigrationResult.ComplexChangedTable(
+                        tableName = fromTable.tableName,
+                        tableNameWithNewPrefix = toTable.newTableName,
+                        oldVersionEntityBundle = fromTable,
+                        newVersionEntityBundle = toTable,
+                        renamedColumnsMap = mutableMapOf()
+                    )
+            }
+            return match.columnName
+        }
+
+        val isColumnDeleted = deletedColumns.any {
+            it.tableName == fromTable.tableName && it.columnName == fromColumn.columnName
+        }
+
+        if (!isColumnDeleted) {
+            // We have encountered an ambiguous scenario, need more input from the user.
+            diffError(
+                deletedOrRenamedColumnFound(
+                    className = className,
+                    tableName = fromTable.tableName,
+                    columnName = fromColumn.columnName
+                )
             )
         }
         return null
     }
 
-    private fun diffError(errorMsg: String) {
+    /**
+     * Checks for complex schema changes at a Table level and returns a ComplexTableChange
+     * including information on which table changes were found on, and whether foreign key or
+     * index related changes have occurred.
+     *
+     * @param fromTable The original version of the table
+     * @param toTable The new version of the table
+     * @return A ComplexChangedTable object, null if complex schema change has not been found
+     */
+    private fun tableContainsComplexChanges(
+        fromTable: EntityBundle,
+        toTable: EntityBundle
+    ): Boolean {
+        if (!isForeignKeyBundlesListEqual(fromTable.foreignKeys, toTable.foreignKeys)) {
+            return true
+        }
+        if (!isIndexBundlesListEqual(fromTable.indices, toTable.indices)) {
+            return true
+        }
+
+        if (!fromTable.primaryKey.isSchemaEqual(toTable.primaryKey)) {
+            return true
+        }
+        // Check if any foreign keys are referencing a renamed table.
+        return fromTable.foreignKeys.any { foreignKey ->
+            renameTableEntries.any {
+                it.originalTableName == foreignKey.table
+            }
+        }
+    }
+
+    /**
+     * Throws a DiffException with the provided error message.
+     *
+     * @param errorMsg Error message to be thrown with the exception
+     */
+    private fun diffError(errorMsg: String): Nothing {
         throw DiffException(errorMsg)
     }
 
@@ -224,6 +360,8 @@
      * Takes in two ForeignKeyBundle lists, attempts to find potential matches based on the columns
      * of the Foreign Keys. Processes these potential matches by checking for schema equality.
      *
+     * @param fromBundle List of foreign keys in the old schema version
+     * @param toBundle List of foreign keys in the new schema version
      * @return true if the two lists of foreign keys are equal
      */
     private fun isForeignKeyBundlesListEqual(
@@ -252,6 +390,8 @@
      * Takes in two IndexBundle lists, attempts to find potential matches based on the names
      * of the indexes. Processes these potential matches by checking for schema equality.
      *
+     * @param fromBundle List of indexes in the old schema version
+     * @param toBundle List of indexes in the new schema version
      * @return true if the two lists of indexes are equal
      */
     private fun isIndexBundlesListEqual(
@@ -272,4 +412,129 @@
         }
         return true
     }
-}
\ No newline at end of file
+
+    /**
+     * Checks if the table provided has been renamed in the new version of the database.
+     *
+     * @param tableName Name of the table in the original database version
+     * @return A RenameTable object if the table has been renamed, otherwise null
+     */
+    private fun isTableRenamed(tableName: String): AutoMigrationResult.RenamedTable? {
+        val annotations = renameTableEntries
+        val renamedTableAnnotations = annotations.filter {
+            it.originalTableName == tableName
+        }
+
+        // Make sure there aren't multiple renames on the same table
+        if (renamedTableAnnotations.size > 1) {
+            diffError(
+                conflictingRenameTableAnnotationsFound(
+                    renamedTableAnnotations.joinToString(",")
+                )
+            )
+        }
+        return renamedTableAnnotations.firstOrNull()
+    }
+
+    /**
+     * Checks if the column provided has been renamed in the new version of the database.
+     *
+     * @param columnName Name of the column in the original database version
+     * @param tableName Name of the table the column belongs to in the original database version
+     * @return A RenameColumn object if the column has been renamed, otherwise null
+     */
+    private fun isColumnRenamed(
+        columnName: String,
+        tableName: String
+    ): AutoMigrationResult.RenamedColumn? {
+        val annotations = renameColumnEntries
+        val renamedColumnAnnotations = annotations.filter {
+            it.originalColumnName == columnName && it.tableName == tableName
+        }
+
+        // Make sure there aren't multiple renames on the same column
+        if (renamedColumnAnnotations.size > 1) {
+            diffError(
+                conflictingRenameColumnAnnotationsFound(renamedColumnAnnotations.joinToString(","))
+            )
+        }
+        return renamedColumnAnnotations.firstOrNull()
+    }
+
+    /**
+     * Looks for any new tables and columns that have been added between versions.
+     *
+     * @param toTable The new version of the table
+     * @param processedTablesAndColumnsInNewVersion List of all columns in the new version of the
+     * database that have been already processed
+     */
+    private fun processAddedTableAndColumns(
+        toTable: EntityBundle,
+        processedTablesAndColumnsInNewVersion: MutableMap<String, List<String>>
+    ) {
+        // Old table bundle will be found even if table is renamed.
+        val isRenamed = renameTableEntries.firstOrNull {
+            it.newTableName == toTable.tableName
+        }
+        val fromTable = if (isRenamed != null) {
+            fromSchemaBundle.entitiesByTableName[isRenamed.originalTableName]
+        } else {
+            fromSchemaBundle.entitiesByTableName[toTable.tableName]
+        }
+
+        if (fromTable == null) {
+            // It's a new table
+            addedTables.add(AutoMigrationResult.AddedTable(toTable))
+            return
+        }
+        val fromColumns = fromTable.fieldsByColumnName
+        val toColumns =
+            processedTablesAndColumnsInNewVersion[toTable.tableName]?.let { processedColumns ->
+                toTable.fieldsByColumnName.filterKeys { !processedColumns.contains(it) }
+            } ?: toTable.fieldsByColumnName
+
+        toColumns.values.forEach { toColumn ->
+            val match = fromColumns[toColumn.columnName]
+            if (match == null) {
+                if (toColumn.isNonNull && toColumn.defaultValue == null) {
+                    diffError(
+                        newNotNullColumnMustHaveDefaultValue(toColumn.columnName)
+                    )
+                }
+                // 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 already with the new
+                // table.
+                if (!complexChangedTables.containsKey(toTable.tableName)) {
+                    addedColumns[toColumn.columnName] =
+                        AutoMigrationResult.AddedColumn(
+                            toTable.tableName,
+                            toColumn
+                        )
+                }
+            }
+        }
+    }
+
+    /**
+     * Goes through the deleted columns list and marks the table of each as a complex changed
+     * table if it was not already.
+     */
+    private fun processDeletedColumns() {
+        deletedColumns.filterNot {
+            complexChangedTables.contains(it.tableName)
+        }.forEach { deletedColumn ->
+            val fromTableBundle =
+                fromSchemaBundle.entitiesByTableName.getValue(deletedColumn.tableName)
+            val toTableBundle =
+                toSchemaBundle.entitiesByTableName.getValue(deletedColumn.tableName)
+            complexChangedTables[deletedColumn.tableName] =
+                AutoMigrationResult.ComplexChangedTable(
+                    tableName = deletedColumn.tableName,
+                    tableNameWithNewPrefix = fromTableBundle.newTableName,
+                    oldVersionEntityBundle = fromTableBundle,
+                    newVersionEntityBundle = toTableBundle,
+                    renamedColumnsMap = mutableMapOf()
+                )
+        }
+    }
+}
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 ba352db..6f5c0a8 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
@@ -31,11 +31,10 @@
     val to: Int?,
     val schemaDiff: SchemaDiffResult
 ) {
-
     val implTypeName: ClassName by lazy {
         ClassName.get(
             element.className.packageName(),
-            "${element.className.simpleNames().joinToString("_")}_Impl"
+            "AutoMigration_${from}_${to}_Impl"
         )
     }
 
@@ -46,14 +45,19 @@
     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 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.
-     *
-     * In the current implementation, we cannot differ between whether the column was removed or
-     * renamed.
+     * Stores the table name, original name, and the new name of a column that was renamed in the
+     * new version of the database.
      */
-    data class RemovedOrRenamedColumn(val tableName: String, val fieldBundle: FieldBundle)
+    data class RenamedColumn(
+        val tableName: String,
+        val originalColumnName: String,
+        val newColumnName: String
+    )
+
+    /**
+     * Stores the table name and the column name of a column that was deleted from the database.
+     */
+    data class DeletedColumn(val tableName: String, val columnName: String)
 
     /**
      * Stores the table that was added to a database in a newer version.
@@ -61,33 +65,36 @@
     data class AddedTable(val entityBundle: EntityBundle)
 
     /**
-     * Stores the table name that contains a change in the primary key, foreign key(s) or index(es)
-     * in a newer version. Explicitly provides information on whether a foreign key change and/or
-     * an index change has occurred.
+     * Stores the table that contains a change in the primary key, foreign key(s) or index(es)
+     * in a newer version, as well as any complex changes and renames on the column-level.
      *
      * As it is possible to have a table with only simple (non-complex) changes, which will be
-     * categorized as "AddedColumn" or "RemovedColumn" changes, all other
+     * categorized as "AddedColumn" or "DeletedColumn" changes, all other
      * changes at the table level are categorized as "complex" changes, using the category
      * "ComplexChangedTable".
      *
-     * At the column level, any change that is not a column add or a
-     * removal will be categorized as "ChangedColumn".
+     * The renamed columns map contains a mapping from the NEW name of the column to the OLD name
+     * of the column.
      */
     data class ComplexChangedTable(
         val tableName: String,
-        val newTableName: String,
+        val tableNameWithNewPrefix: String,
         val oldVersionEntityBundle: EntityBundle,
         val newVersionEntityBundle: EntityBundle,
-        val foreignKeyChanged: Boolean,
-        val indexChanged: Boolean
+        val renamedColumnsMap: MutableMap<String, String>
     )
 
     /**
-     * Stores the table 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.
+     * Stores the original name and the new name of a table that was renamed in the
+     * new version of the database.
      *
-     * In the current implementation, we cannot differ between whether the table was removed or
-     * renamed.
+     * This container will only be used for tables that got renamed, but do not have any complex
+     * changes on it, both on the table and column level.
      */
-    data class RemovedOrRenamedTable(val entityBundle: EntityBundle)
+    data class RenamedTable(val originalTableName: String, val newTableName: String)
+
+    /**
+     * Stores the name of the table that was deleted from the database.
+     */
+    data class DeletedTable(val deletedTableName: String)
 }
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 9748802..254e282 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -23,6 +23,7 @@
 import androidx.room.ext.RoomTypeNames
 import androidx.room.ext.S
 import androidx.room.ext.SupportDbTypeNames
+import androidx.room.migration.bundle.EntityBundle
 import androidx.room.vo.AutoMigrationResult
 import com.squareup.javapoet.MethodSpec
 import com.squareup.javapoet.ParameterSpec
@@ -38,11 +39,11 @@
     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
+    private val addedColumns = autoMigrationResult.schemaDiff.addedColumns
+    private val addedTables = autoMigrationResult.schemaDiff.addedTables
+    private val renamedTables = autoMigrationResult.schemaDiff.renamedTables
+    private val complexChangedTables = autoMigrationResult.schemaDiff.complexChangedTables
+    private val deletedTables = autoMigrationResult.schemaDiff.deletedTables
 
     override fun createTypeSpecBuilder(): TypeSpec.Builder {
         val builder = TypeSpec.classBuilder(autoMigrationResult.implTypeName)
@@ -94,9 +95,7 @@
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
     private fun addAutoMigrationResultToMigrate(migrateBuilder: MethodSpec.Builder) {
-        if (complexChangedTables.isNotEmpty()) {
-            addComplexChangeStatements(migrateBuilder)
-        }
+        addComplexChangeStatements(migrateBuilder)
         addSimpleChangeStatements(migrateBuilder)
     }
 
@@ -107,15 +106,34 @@
      * @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)
+        complexChangedTables.values.forEach {
+            (
+                _,
+                tableNameWithNewPrefix,
+                oldEntityBundle,
+                newEntityBundle,
+                renamedColumnsMap
+            ) ->
+
+            addStatementsToCreateNewTable(newEntityBundle, migrateBuilder)
+            addStatementsToContentTransfer(
+                oldEntityBundle.tableName,
+                tableNameWithNewPrefix,
+                oldEntityBundle,
+                newEntityBundle,
+                renamedColumnsMap,
+                migrateBuilder
+            )
+            addStatementsToDropTableAndRenameTempTable(
+                oldEntityBundle.tableName,
+                newEntityBundle.tableName,
+                tableNameWithNewPrefix,
+                migrateBuilder
+            )
+            addStatementsToRecreateIndexes(newEntityBundle, migrateBuilder)
+            if (newEntityBundle.foreignKeys.isNotEmpty()) {
+                addStatementsToCheckForeignKeyConstraint(newEntityBundle.tableName, migrateBuilder)
+            }
         }
     }
 
@@ -126,55 +144,62 @@
      *
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
-    // TODO: (b/183007590) Handle column/table renames here
     private fun addSimpleChangeStatements(migrateBuilder: MethodSpec.Builder) {
-
-        if (addedColumns.isNotEmpty()) {
-            addNewColumnStatements(migrateBuilder)
-        }
-
-        if (addedTables.isNotEmpty()) {
-            addNewTableStatements(migrateBuilder)
-        }
+        addDeleteTableStatements(migrateBuilder)
+        addRenameTableStatements(migrateBuilder)
+        addNewColumnStatements(migrateBuilder)
+        addNewTableStatements(migrateBuilder)
     }
 
     /**
      * Adds the SQL statements for creating a new table in the desired revised format of table.
      *
+     * @param newTable Schema of the new table to be created
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
     private fun addStatementsToCreateNewTable(
-        table: AutoMigrationResult.ComplexChangedTable,
+        newTable: EntityBundle,
         migrateBuilder: MethodSpec.Builder
     ) {
         addDatabaseExecuteSqlStatement(
             migrateBuilder,
-            table.newVersionEntityBundle.createNewTable()
+            newTable.createNewTable()
         )
     }
 
     /**
      * Adds the SQL statements for transferring the contents of the old table to the new version.
      *
+     * @param oldTableName Name of the table in the old version of the database
+     * @param tableNameWithNewPrefix Name of the table with the '_new_' prefix added
+     * @param oldEntityBundle Entity bundle of the table in the old version of the database
+     * @param newEntityBundle Entity bundle of the table in the new version of the database
+     * @param renamedColumnsMap Map of the renamed columns of the table (new name -> old name)
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
     private fun addStatementsToContentTransfer(
-        table: AutoMigrationResult.ComplexChangedTable,
+        oldTableName: String,
+        tableNameWithNewPrefix: String,
+        oldEntityBundle: EntityBundle,
+        newEntityBundle: EntityBundle,
+        renamedColumnsMap: MutableMap<String, String>,
         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`" }
+        val newColumnSequence = newEntityBundle.fieldsByColumnName.keys.filter {
+            oldEntityBundle.fieldsByColumnName.keys.contains(it) ||
+                renamedColumnsMap.containsKey(it)
+        }
+        val oldColumnSequence = mutableListOf<String>()
+        newColumnSequence.forEach { column ->
+            oldColumnSequence.add(renamedColumnsMap[column] ?: column)
+        }
 
         addDatabaseExecuteSqlStatement(
             migrateBuilder,
             buildString {
                 append(
-                    "INSERT INTO `${table.newTableName}` ($newColumnSequence) " +
-                        "SELECT $oldColumnSequence FROM `${table.tableName}`"
+                    "INSERT INTO `$tableNameWithNewPrefix` (${newColumnSequence.joinToString(",")
+                    }) SELECT ${oldColumnSequence.joinToString(",")} FROM `$oldTableName`",
                 )
             }
         )
@@ -184,32 +209,38 @@
      * 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 oldTableName Name of the table in the old version of the database
+     * @param newTableName Name of the table in the new version of the database
+     * @param tableNameWithNewPrefix Name of the table with the '_new_' prefix added
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
     private fun addStatementsToDropTableAndRenameTempTable(
-        table: AutoMigrationResult.ComplexChangedTable,
+        oldTableName: String,
+        newTableName: String,
+        tableNameWithNewPrefix: String,
         migrateBuilder: MethodSpec.Builder
     ) {
         addDatabaseExecuteSqlStatement(
             migrateBuilder,
-            "DROP TABLE `${table.tableName}`"
+            "DROP TABLE `$oldTableName`"
         )
         addDatabaseExecuteSqlStatement(
             migrateBuilder,
-            "ALTER TABLE `${table.newTableName}` RENAME TO `${table.tableName}`"
+            "ALTER TABLE `$tableNameWithNewPrefix` RENAME TO `$newTableName`"
         )
     }
 
     /**
      * Adds the SQL statements for recreating indexes.
      *
+     * @param table The table the indexes of which will be recreated
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
     private fun addStatementsToRecreateIndexes(
-        table: AutoMigrationResult.ComplexChangedTable,
+        table: EntityBundle,
         migrateBuilder: MethodSpec.Builder
     ) {
-        table.newVersionEntityBundle.indices.forEach { index ->
+        table.indices.forEach { index ->
             addDatabaseExecuteSqlStatement(
                 migrateBuilder,
                 index.getCreateSql(table.tableName)
@@ -220,19 +251,58 @@
     /**
      * Adds the SQL statement for checking the foreign key constraints.
      *
+     * @param tableName Name of the table
      * @param migrateBuilder Builder for the migrate() function to be generated
      */
     private fun addStatementsToCheckForeignKeyConstraint(
-        table: AutoMigrationResult.ComplexChangedTable,
+        tableName: String,
         migrateBuilder: MethodSpec.Builder
     ) {
         addDatabaseExecuteSqlStatement(
             migrateBuilder,
-            "PRAGMA foreign_key_check(`${table.tableName}`)"
+            "PRAGMA foreign_key_check(`$tableName`)"
         )
     }
 
     /**
+     * Adds the SQL statements for removing a table.
+     *
+     * @param migrateBuilder Builder for the migrate() function to be generated
+     */
+    private fun addDeleteTableStatements(migrateBuilder: MethodSpec.Builder) {
+        deletedTables.forEach { tableName ->
+            val deleteTableSql = buildString {
+                append(
+                    "DROP TABLE `$tableName`"
+                )
+            }
+            addDatabaseExecuteSqlStatement(
+                migrateBuilder,
+                deleteTableSql
+            )
+        }
+    }
+
+    /**
+     * Adds the SQL statements for renaming a table.
+     *
+     * @param migrateBuilder Builder for the migrate() function to be generated
+     */
+    private fun addRenameTableStatements(migrateBuilder: MethodSpec.Builder) {
+        renamedTables.forEach { (oldName, newName) ->
+            val renameTableSql = buildString {
+                append(
+                    "ALTER TABLE `$oldName` RENAME TO `$newName`"
+                )
+            }
+            addDatabaseExecuteSqlStatement(
+                migrateBuilder,
+                renameTableSql
+            )
+        }
+    }
+
+    /**
      * Adds the SQL statements for adding new columns to a table.
      *
      * @param migrateBuilder Builder for the migrate() function to be generated
@@ -276,13 +346,15 @@
      * database.
      *
      * @param migrateBuilder Builder for the migrate() function to be generated
+     * @param sql The SQL statement to be executed by the database
      */
     private fun addDatabaseExecuteSqlStatement(
         migrateBuilder: MethodSpec.Builder,
         sql: String
     ) {
         migrateBuilder.addStatement(
-            "database.execSQL($S)", sql
+            "database.execSQL($S)",
+            sql
         )
     }
 }
diff --git a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
index c75033d..e799cad 100644
--- a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
+++ b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
@@ -10,8 +10,8 @@
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
-class ValidAutoMigrationWithDefault_Impl extends Migration implements AutoMigrationCallback {
-    public ValidAutoMigrationWithDefault_Impl() {
+class AutoMigration_1_2_Impl extends Migration implements AutoMigrationCallback {
+    public AutoMigration_1_2_Impl() {
         super(1, 2);
     }
 
diff --git a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
index 93eb47d..ed2a2a9 100644
--- a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
+++ b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
@@ -10,8 +10,8 @@
 
 @Generated("androidx.room.RoomProcessor")
 @SuppressWarnings({"unchecked", "deprecation"})
-class ValidAutoMigrationWithoutDefault_Impl extends Migration implements AutoMigrationCallback {
-    public ValidAutoMigrationWithoutDefault_Impl() {
+class AutoMigration_1_2_Impl extends Migration implements AutoMigrationCallback {
+    public AutoMigration_1_2_Impl() {
         super(1, 2);
     }
 
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
index be99a31..886bfbd 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/AutoMigrationProcessorTest.kt
@@ -23,6 +23,7 @@
 import androidx.room.migration.bundle.FieldBundle
 import androidx.room.migration.bundle.PrimaryKeyBundle
 import androidx.room.migration.bundle.SchemaBundle
+import androidx.room.processor.ProcessorErrors.autoMigrationElementMustExtendCallback
 import androidx.room.testing.context
 import org.junit.Test
 
@@ -34,19 +35,21 @@
             """
             package foo.bar;
             import androidx.room.AutoMigration;
-            @AutoMigration(from=1, to=2)
             class MyAutoMigration implements AutoMigration {}
             """.trimIndent()
         )
 
         runProcessorTest(listOf(source)) { invocation ->
             AutoMigrationProcessor(
-                invocation.context,
                 invocation.processingEnv.requireTypeElement("foo.bar.MyAutoMigration"),
+                invocation.context,
+                1,
+                2,
+                invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
                 from.database
             ).process()
             invocation.assertCompilationResult {
-                hasError(ProcessorErrors.AUTOMIGRATION_ANNOTATED_TYPE_ELEMENT_MUST_BE_INTERFACE)
+                hasError(ProcessorErrors.AUTOMIGRATION_CALLBACK_MUST_BE_INTERFACE)
             }
         }
     }
@@ -66,13 +69,16 @@
 
         runProcessorTest(listOf(source)) { invocation ->
             AutoMigrationProcessor(
-                invocation.context,
                 invocation.processingEnv.requireTypeElement("foo.bar.MyAutoMigration"),
+                invocation.context,
+                1,
+                2,
+                invocation.processingEnv.requireType("foo.bar.MyAutoMigration"),
                 from.database
             ).process()
             invocation.assertCompilationResult {
                 hasError(
-                    ProcessorErrors.AUTOMIGRATION_ELEMENT_MUST_IMPLEMENT_AUTOMIGRATION_CALLBACK
+                    autoMigrationElementMustExtendCallback("MyAutoMigration")
                 )
             }
         }
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 700eb3c..baf2464 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -1214,7 +1214,8 @@
         singleDb(
             """
                 @Database(entities = {User.class}, version = 42, exportSchema = false,
-                autoMigrations = {MyAutoMigration.class})
+                autoMigrations = {@AutoMigration(from = 1, to = 2, callback = MyAutoMigration
+                .class), @AutoMigration(from = 2, to = 3)})
                 public abstract class MyDb extends RoomDatabase {}
                 """,
             USER, AUTOMIGRATION
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 402e271..c416c40 100644
--- a/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/util/SchemaDifferTest.kt
@@ -34,7 +34,12 @@
     fun testPrimaryKeyChanged() {
         val diffResult = SchemaDiffer(
             fromSchemaBundle = from.database,
-            toSchemaBundle = toChangeInPrimaryKey.database
+            toSchemaBundle = toChangeInPrimaryKey.database,
+            className = "MyAutoMigration",
+            renameColumnEntries = listOf(),
+            deleteColumnEntries = listOf(),
+            renameTableEntries = listOf(),
+            deleteTableEntries = listOf()
         ).diffSchemas()
 
         assertThat(diffResult.complexChangedTables.keys).contains("Song")
@@ -44,31 +49,42 @@
     fun testForeignKeyFieldChanged() {
         val diffResult = SchemaDiffer(
             fromSchemaBundle = from.database,
-            toSchemaBundle = toForeignKeyAdded.database
+            toSchemaBundle = toForeignKeyAdded.database,
+            className = "MyAutoMigration",
+            renameColumnEntries = listOf(),
+            deleteColumnEntries = listOf(),
+            renameTableEntries = listOf(),
+            deleteTableEntries = listOf()
         ).diffSchemas()
 
-        assertThat(diffResult.complexChangedTables.isNotEmpty())
-        assertThat(diffResult.complexChangedTables["Song"]?.foreignKeyChanged).isTrue()
-        assertThat(diffResult.complexChangedTables["Song"]?.indexChanged).isFalse()
+        assertThat(diffResult.complexChangedTables["Song"] != null)
     }
 
     @Test
     fun testComplexChangeInvolvingIndex() {
         val diffResult = SchemaDiffer(
             fromSchemaBundle = from.database,
-            toSchemaBundle = toIndexAdded.database
+            toSchemaBundle = toIndexAdded.database,
+            className = "MyAutoMigration",
+            renameColumnEntries = listOf(),
+            deleteColumnEntries = listOf(),
+            renameTableEntries = listOf(),
+            deleteTableEntries = listOf()
         ).diffSchemas()
 
-        assertThat(diffResult.complexChangedTables.isNotEmpty())
-        assertThat(diffResult.complexChangedTables["Song"]?.foreignKeyChanged).isFalse()
-        assertThat(diffResult.complexChangedTables["Song"]?.indexChanged).isTrue()
+        assertThat(diffResult.complexChangedTables["Song"] != null)
     }
 
     @Test
     fun testColumnAddedWithColumnInfoDefaultValue() {
         val schemaDiffResult = SchemaDiffer(
             fromSchemaBundle = from.database,
-            toSchemaBundle = toColumnAddedWithColumnInfoDefaultValue.database
+            toSchemaBundle = toColumnAddedWithColumnInfoDefaultValue.database,
+            className = "MyAutoMigration",
+            renameColumnEntries = listOf(),
+            deleteColumnEntries = listOf(),
+            renameTableEntries = listOf(),
+            deleteTableEntries = listOf()
         ).diffSchemas()
         assertThat(schemaDiffResult.addedColumns["artistId"]?.fieldBundle?.columnName)
             .isEqualTo("artistId")
@@ -79,7 +95,12 @@
         try {
             SchemaDiffer(
                 fromSchemaBundle = from.database,
-                toSchemaBundle = toColumnAddedWithNoDefaultValue.database
+                toSchemaBundle = toColumnAddedWithNoDefaultValue.database,
+                className = "MyAutoMigration",
+                renameColumnEntries = listOf(),
+                deleteColumnEntries = listOf(),
+                renameTableEntries = listOf(),
+                deleteTableEntries = listOf()
             ).diffSchemas()
             fail("DiffException should have been thrown.")
         } catch (ex: DiffException) {
@@ -93,10 +114,15 @@
     fun testTableAddedWithColumnInfoDefaultValue() {
         val schemaDiffResult = SchemaDiffer(
             fromSchemaBundle = from.database,
-            toSchemaBundle = toTableAddedWithColumnInfoDefaultValue.database
+            toSchemaBundle = toTableAddedWithColumnInfoDefaultValue.database,
+            className = "MyAutoMigration",
+            renameColumnEntries = listOf(),
+            deleteColumnEntries = listOf(),
+            renameTableEntries = listOf(),
+            deleteTableEntries = listOf()
         ).diffSchemas()
-        assertThat(schemaDiffResult.addedTables[0].entityBundle.tableName).isEqualTo("Artist")
-        assertThat(schemaDiffResult.addedTables[1].entityBundle.tableName).isEqualTo("Album")
+        assertThat(schemaDiffResult.addedTables.toList()[0].entityBundle.tableName)
+            .isEqualTo("Album")
     }
 
     @Test
@@ -104,12 +130,17 @@
         try {
             SchemaDiffer(
                 fromSchemaBundle = from.database,
-                toSchemaBundle = toColumnRenamed.database
+                toSchemaBundle = toColumnRenamed.database,
+                className = "MyAutoMigration",
+                renameColumnEntries = listOf(),
+                deleteColumnEntries = listOf(),
+                renameTableEntries = listOf(),
+                deleteTableEntries = listOf()
             ).diffSchemas()
             fail("DiffException should have been thrown.")
         } catch (ex: DiffException) {
             assertThat(ex.errorMessage).isEqualTo(
-                ProcessorErrors.removedOrRenamedColumnFound("length")
+                ProcessorErrors.deletedOrRenamedColumnFound("MyAutoMigration", "length", "Song")
             )
         }
     }
@@ -119,12 +150,57 @@
         try {
             SchemaDiffer(
                 fromSchemaBundle = from.database,
-                toSchemaBundle = toColumnRemoved.database
+                toSchemaBundle = toColumnRemoved.database,
+                className = "MyAutoMigration",
+                renameColumnEntries = listOf(),
+                deleteColumnEntries = listOf(),
+                renameTableEntries = listOf(),
+                deleteTableEntries = listOf()
             ).diffSchemas()
             fail("DiffException should have been thrown.")
         } catch (ex: DiffException) {
             assertThat(ex.errorMessage).isEqualTo(
-                ProcessorErrors.removedOrRenamedColumnFound("length")
+                ProcessorErrors.deletedOrRenamedColumnFound("MyAutoMigration", "length", "Song")
+            )
+        }
+    }
+
+    @Test
+    fun testTableRenamedWithoutAnnotation() {
+        try {
+            SchemaDiffer(
+                fromSchemaBundle = from.database,
+                toSchemaBundle = toTableRenamed.database,
+                className = "MyAutoMigration",
+                renameColumnEntries = listOf(),
+                deleteColumnEntries = listOf(),
+                renameTableEntries = listOf(),
+                deleteTableEntries = listOf()
+            ).diffSchemas()
+            fail("DiffException should have been thrown.")
+        } catch (ex: DiffException) {
+            assertThat(ex.errorMessage).isEqualTo(
+                ProcessorErrors.deletedOrRenamedTableFound("MyAutoMigration", "Artist")
+            )
+        }
+    }
+
+    @Test
+    fun testTableRemovedWithoutAnnotation() {
+        try {
+            SchemaDiffer(
+                fromSchemaBundle = from.database,
+                toSchemaBundle = toTableDeleted.database,
+                className = "MyAutoMigration",
+                renameColumnEntries = listOf(),
+                deleteColumnEntries = listOf(),
+                renameTableEntries = listOf(),
+                deleteTableEntries = listOf()
+            ).diffSchemas()
+            fail("DiffException should have been thrown.")
+        } catch (ex: DiffException) {
+            assertThat(ex.errorMessage).isEqualTo(
+                ProcessorErrors.deletedOrRenamedTableFound("MyAutoMigration", "Artist")
             )
         }
     }
@@ -168,6 +244,40 @@
                     ),
                     mutableListOf(),
                     mutableListOf()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -176,6 +286,132 @@
     )
 
     /** Valid "to" Schemas */
+    val toTableRenamed = SchemaBundle(
+        2,
+        DatabaseBundle(
+            2,
+            "",
+            mutableListOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
+                ),
+                EntityBundle(
+                    "Album",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+
+    val toTableDeleted = SchemaBundle(
+        2,
+        DatabaseBundle(
+            2,
+            "",
+            mutableListOf(
+                EntityBundle(
+                    "Song",
+                    "CREATE TABLE IF NOT EXISTS `Song` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
+                )
+            ),
+            mutableListOf(),
+            mutableListOf()
+        )
+    )
+
     val toColumnAddedWithColumnInfoDefaultValue = SchemaBundle(
         2,
         DatabaseBundle(
@@ -223,6 +459,40 @@
                     ),
                     emptyList(),
                     emptyList()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -268,6 +538,40 @@
                     ),
                     emptyList(),
                     emptyList()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -327,6 +631,40 @@
                     ),
                     emptyList(),
                     emptyList()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -379,6 +717,40 @@
                     ),
                     mutableListOf(),
                     mutableListOf()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -390,7 +762,6 @@
      * The affinity of a length column is changed from Integer to Text. No columns are
      * added/removed.
      */
-    // TODO: We currently do not support column affinity changes.
     val toColumnAffinityChanged = SchemaBundle(
         2,
         DatabaseBundle(
@@ -431,6 +802,40 @@
                     ),
                     mutableListOf(),
                     mutableListOf()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -480,20 +885,37 @@
                 ),
                 EntityBundle(
                     "Artist",
-                    "CREATE TABLE IF NOT EXISTS `Artist` (`artistId` INTEGER NOT NULL, `name` " +
-                        "TEXT NOT NULL, PRIMARY KEY(`artistId`))",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
                     listOf(
                         FieldBundle(
-                            "artistId",
-                            "artistId",
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
                             "INTEGER",
                             true,
                             "1"
                         )
                     ),
-                    PrimaryKeyBundle(true, listOf("artistId")),
-                    listOf(),
-                    listOf()
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 ),
                 EntityBundle(
                     "Album",
@@ -575,6 +997,40 @@
                             listOf("artistId")
                         )
                     )
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -629,6 +1085,40 @@
                         )
                     ),
                     mutableListOf()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
@@ -675,6 +1165,40 @@
                     ),
                     mutableListOf(),
                     mutableListOf()
+                ),
+                EntityBundle(
+                    "Artist",
+                    "CREATE TABLE IF NOT EXISTS `Artist` (`id` INTEGER NOT NULL, " +
+                        "`title` TEXT NOT NULL, `length` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+                    listOf(
+                        FieldBundle(
+                            "id",
+                            "id",
+                            "INTEGER",
+                            true,
+                            "1"
+                        ),
+                        FieldBundle(
+                            "title",
+                            "title",
+                            "TEXT",
+                            true,
+                            ""
+                        ),
+                        FieldBundle(
+                            "length",
+                            "length",
+                            "INTEGER",
+                            true,
+                            "1"
+                        )
+                    ),
+                    PrimaryKeyBundle(
+                        false,
+                        mutableListOf("id")
+                    ),
+                    mutableListOf(),
+                    mutableListOf()
                 )
             ),
             mutableListOf(),
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 4da432f..9497ce2 100644
--- a/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
@@ -36,7 +36,6 @@
             """
             package foo.bar;
             import androidx.room.migration.AutoMigrationCallback;
-            import androidx.room.AutoMigration;
             import androidx.sqlite.db.SupportSQLiteDatabase;
             interface ValidAutoMigrationWithDefault extends AutoMigrationCallback {}
             """.trimIndent()
@@ -65,10 +64,11 @@
                             )
                         )
                     ),
-                    removedOrRenamedColumns = listOf(),
-                    addedTables = listOf(),
+                    deletedColumns = listOf(),
+                    addedTables = setOf(),
                     complexChangedTables = mapOf(),
-                    removedOrRenamedTables = listOf()
+                    renamedTables = mapOf(),
+                    deletedTables = listOf()
                 ),
             )
             AutoMigrationWriter(
@@ -82,7 +82,7 @@
                     loadTestSource(
                         "autoMigrationWriter/output/ValidAutoMigrationWithDefault" +
                             ".java",
-                        "foo.bar.ValidAutoMigrationWithDefault_Impl"
+                        "foo.bar.AutoMigration_1_2_Impl"
                     )
                 )
             }
@@ -96,9 +96,7 @@
             """
             package foo.bar;
             import androidx.room.migration.AutoMigrationCallback;
-            import androidx.room.AutoMigration;
             import androidx.sqlite.db.SupportSQLiteDatabase;
-            @AutoMigration(from=1, to=2)
             interface ValidAutoMigrationWithoutDefault extends AutoMigrationCallback {}
             """.trimIndent()
         )
@@ -126,10 +124,11 @@
                             )
                         )
                     ),
-                    removedOrRenamedColumns = listOf(),
-                    addedTables = listOf(),
+                    deletedColumns = listOf(),
+                    addedTables = setOf(),
                     complexChangedTables = mapOf(),
-                    removedOrRenamedTables = listOf()
+                    renamedTables = mapOf(),
+                    deletedTables = listOf()
                 ),
             )
             AutoMigrationWriter(
@@ -142,7 +141,7 @@
                 generatedSource(
                     loadTestSource(
                         "autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java",
-                        "foo.bar.ValidAutoMigrationWithoutDefault_Impl"
+                        "foo.bar.AutoMigration_1_2_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 c02e4d1..8b7d1c0 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
@@ -467,6 +467,204 @@
           }
         ],
         "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "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": "Entity16",
+        "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": "Entity17",
+        "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": "Entity18",
+        "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": "Entity19",
+        "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": "Entity20",
+        "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": []
       }
     ],
     "views": [],
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 6ebbddb..a925d683 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,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 2,
-    "identityHash": "cb0906b793f21e53ed5ff0db35729db5",
+    "identityHash": "083a4e91debef71d5d900ca2cf0baccf",
     "entities": [
       {
         "tableName": "Entity1",
@@ -314,7 +314,7 @@
       },
       {
         "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)",
+        "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_V2`(`addedInV1`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
         "fields": [
           {
             "fieldPath": "id",
@@ -354,7 +354,7 @@
         ],
         "foreignKeys": [
           {
-            "table": "Entity13",
+            "table": "Entity13_V2",
             "onDelete": "NO ACTION",
             "onUpdate": "NO ACTION",
             "columns": [
@@ -442,7 +442,7 @@
         "foreignKeys": []
       },
       {
-        "tableName": "Entity13",
+        "tableName": "Entity13_V2",
         "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
         "fields": [
           {
@@ -473,12 +473,12 @@
         },
         "indices": [
           {
-            "name": "index_Entity13_addedInV1",
+            "name": "index_Entity13_V2_addedInV1",
             "unique": true,
             "columnNames": [
               "addedInV1"
             ],
-            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_V2_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
           }
         ],
         "foreignKeys": []
@@ -508,12 +508,177 @@
         },
         "indices": [],
         "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "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": "Entity16",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `renamedInV2` 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": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity17",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `renamedInV2` TEXT DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity19_V2",
+        "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": "Entity20_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` TEXT DEFAULT '1', `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "name"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "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, 'cb0906b793f21e53ed5ff0db35729db5')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '083a4e91debef71d5d900ca2cf0baccf')"
     ]
   }
 }
\ 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 ce96535..db63fc3 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
@@ -22,11 +22,15 @@
 import androidx.room.ColumnInfo;
 import androidx.room.Dao;
 import androidx.room.Database;
+import androidx.room.DeleteColumn;
+import androidx.room.DeleteTable;
 import androidx.room.Entity;
 import androidx.room.ForeignKey;
 import androidx.room.Index;
 import androidx.room.PrimaryKey;
 import androidx.room.Query;
+import androidx.room.RenameColumn;
+import androidx.room.RenameTable;
 import androidx.room.RoomDatabase;
 import androidx.room.migration.AutoMigrationCallback;
 
@@ -47,15 +51,28 @@
                 AutoMigrationDb.Entity10.class,
                 AutoMigrationDb.Entity11.class,
                 AutoMigrationDb.Entity12.class,
-                AutoMigrationDb.Entity13.class,
-                AutoMigrationDb.Entity14.class
+                AutoMigrationDb.Entity13_V2.class,
+                AutoMigrationDb.Entity14.class,
+                AutoMigrationDb.Entity15.class,
+                AutoMigrationDb.Entity16.class,
+                AutoMigrationDb.Entity17.class,
+                AutoMigrationDb.Entity19_V2.class,
+                AutoMigrationDb.Entity20_V2.class
         },
-        autoMigrations = AutoMigrationDb.SimpleAutoMigration.class,
+        autoMigrations = {
+                @AutoMigration(
+                        from = 1, to = 2, callback = AutoMigrationDb.SimpleAutoMigration1.class
+                )
+        },
         exportSchema = true
 )
 public abstract class AutoMigrationDb extends RoomDatabase {
     static final int LATEST_VERSION = 2;
     abstract AutoMigrationDb.AutoMigrationDao dao();
+
+    /**
+     * No change between versions.
+     */
     @Entity
     static class Entity1 {
         public static final String TABLE_NAME = "Entity1";
@@ -66,6 +83,9 @@
         public int addedInV1;
     }
 
+    /**
+     * A new simple column added to Entity 2 with a default value.
+     */
     @Entity
     static class Entity2 {
         public static final String TABLE_NAME = "Entity2";
@@ -78,6 +98,9 @@
         public int addedInV2;
     }
 
+    /**
+     * Added Entity 3 to the schema. No foreign keys, views, indices added.
+     */
     @Entity
     static class Entity3 {
         public static final String TABLE_NAME = "Entity3";
@@ -86,6 +109,9 @@
         public String name;
     }
 
+    /**
+     * Changing the default value of ‘addedInV1’ in Entity 4.
+     */
     @Entity
     static class Entity4 {
         public static final String TABLE_NAME = "Entity4";
@@ -96,6 +122,9 @@
         public int addedInV1;
     }
 
+    /**
+     * Changing the affinity of ‘addedInV1’ in Entity 5.
+     */
     @Entity
     static class Entity5 {
         public static final String TABLE_NAME = "Entity5";
@@ -106,6 +135,9 @@
         public String addedInV1;
     }
 
+    /**
+     * Changing the nullability of ‘addedInV1’ in Entity 6.
+     */
     @Entity
     static class Entity6 {
         public static final String TABLE_NAME = "Entity6";
@@ -116,6 +148,9 @@
         public int addedInV1;
     }
 
+    /**
+     * No change between versions.
+     */
     @Entity
     static class Entity7 {
         public static final String TABLE_NAME = "Entity7";
@@ -126,6 +161,9 @@
         public int addedInV1;
     }
 
+    /**
+     * Change the primary key of Entity 8.
+     */
     @Entity
     static class Entity8 {
         public static final String TABLE_NAME = "Entity8";
@@ -137,6 +175,9 @@
         public int addedInV1;
     }
 
+    /**
+     * Add a foreign key to Entity 9.
+     */
     @Entity(foreignKeys = {
             @ForeignKey(entity = Entity12.class,
                     parentColumns = "id",
@@ -151,8 +192,13 @@
         public int addedInV1;
     }
 
+    /**
+     * Change the foreign key added in Entity 10 to ‘addedInV1’. Add index for addedInV1 on
+     * Entity 10. The reference table of the foreign key has been renamed from Entity13 to
+     * Entity13_V2.
+     */
     @Entity(foreignKeys = {
-            @ForeignKey(entity = Entity13.class,
+            @ForeignKey(entity = Entity13_V2.class,
                     parentColumns = "addedInV1",
                     childColumns = "addedInV1",
                     deferred = true)},
@@ -166,6 +212,9 @@
         public int addedInV1;
     }
 
+    /**
+     * Remove the foreign key in Entity 11.
+     */
     @Entity
     static class Entity11 {
         public static final String TABLE_NAME = "Entity11";
@@ -176,6 +225,9 @@
         public int addedInV1;
     }
 
+    /**
+     * Add an index ‘name’ to Entity 12.
+     */
     @Entity(indices = {@Index(value = "name", unique = true)})
     static class Entity12 {
         public static final String TABLE_NAME = "Entity12";
@@ -186,8 +238,12 @@
         public int addedInV1;
     }
 
+    /**
+     * Rename to Entity13_V2, it is a table referenced by the foreign key in Entity10. Change the
+     * index added in Entity 13 to ‘addedInV1’.
+     */
     @Entity(indices = {@Index(value = "addedInV1", unique = true)})
-    static class Entity13 {
+    static class Entity13_V2 {
         public static final String TABLE_NAME = "Entity13";
         @PrimaryKey
         public int id;
@@ -196,6 +252,9 @@
         public int addedInV1;
     }
 
+    /**
+     * Remove the index ‘name’ added in Entity 14.
+     */
     @Entity
     static class Entity14 {
         public static final String TABLE_NAME = "Entity14";
@@ -204,14 +263,94 @@
         public String name;
     }
 
+    /**
+     * Deleting the column ‘addedInV1’ from Entity 15.
+     */
+    @Entity
+    static class Entity15 {
+        public static final String TABLE_NAME = "Entity15";
+        @PrimaryKey
+        public int id;
+        public String name;
+    }
+
+    /**
+     * Renaming the column ‘addedInV1’ from Entity 16 to ‘renamedInV2’.
+     */
+    @Entity
+    static class Entity16 {
+        public static final String TABLE_NAME = "Entity16";
+        @PrimaryKey
+        public int id;
+        public String name;
+        @ColumnInfo(defaultValue = "1")
+        public int renamedInV2;
+    }
+
+    /**
+     * Renaming the column ‘addedInV1’ from Entity 17 to ‘renamedInV2’. Changing the affinity of
+     * this column.
+     */
+    @Entity
+    static class Entity17 {
+        public static final String TABLE_NAME = "Entity17";
+        @PrimaryKey
+        public int id;
+        public String name;
+        @ColumnInfo(defaultValue = "1")
+        public String renamedInV2;
+    }
+
+    /**
+     * Deleted Entity 18.
+     *
+     * Rename Entity19 to ‘Entity19_V2’.
+     */
+    @Entity
+    static class Entity19_V2 {
+        public static final String TABLE_NAME = "Entity19_V2";
+        @PrimaryKey
+        public int id;
+        public String name;
+        @ColumnInfo(defaultValue = "1")
+        public int addedInV1;
+    }
+
+    /**
+     * Rename Entity20 to ‘Entity20_V2’. Rename the column ‘addedInV1’ to ‘renamedInV2’. Change
+     * the primary key of this table to ‘name’. Change the affinity of the column ‘renamedInV2’.
+     * Add new column ‘addedInV2’.
+     */
+    @Entity
+    static class Entity20_V2 {
+        public static final String TABLE_NAME = "Entity20_V2";
+        public int id;
+        @PrimaryKey
+        @NonNull
+        public String name;
+        @ColumnInfo(defaultValue = "1")
+        public String renamedInV2;
+        @ColumnInfo(defaultValue = "2")
+        public int addedInV2;
+    }
+
     @Dao
     interface AutoMigrationDao {
         @Query("SELECT * from Entity1 ORDER BY id ASC")
         List<AutoMigrationDb.Entity1> getAllEntity1s();
     }
 
-    @AutoMigration(from=1, to=2)
-    interface SimpleAutoMigration extends AutoMigrationCallback {
-
+    @DeleteTable(deletedTableName = "Entity18")
+    @RenameTable(originalTableName = "Entity19", newTableName = "Entity19_V2")
+    @RenameTable(originalTableName = "Entity20", newTableName = "Entity20_V2")
+    @RenameTable(originalTableName = "Entity13", newTableName = "Entity13_V2")
+    @RenameColumn(tableName = "Entity16", originalColumnName = "addedInV1",
+            newColumnName = "renamedInV2")
+    @RenameColumn(tableName = "Entity17", originalColumnName = "addedInV1",
+            newColumnName = "renamedInV2")
+    @RenameColumn(tableName = "Entity20", originalColumnName = "addedInV1",
+            newColumnName = "renamedInV2")
+    @DeleteColumn(tableName = "Entity15", deletedColumnName = "addedInV1")
+    interface SimpleAutoMigration1 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 5000a72..7a45a06 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
@@ -48,6 +48,12 @@
                 AutoMigrationDb.class.getCanonicalName());
     }
 
+    // Run this to create the very 1st version of the db.
+    public void createFirstVersion() throws IOException {
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
+        db.close();
+    }
+
     @Test
     public void testBadAutoMigrationInput() throws IOException {
         try (SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1)) {
@@ -57,15 +63,15 @@
                     TEST_DB,
                     2,
                     true,
-                    autoMigrationDbV2.getAutoGeneratedMigration(2, 3)
+                    autoMigrationDbV2.getAutoGeneratedMigration(3, 4)
             );
             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.")
+                    is("No AutoMigrations between versions 'from = 3', 'to = "
+                    + "4' have been provided. Annotate Database class with @AutoMigration(from = "
+                    + "3, to = 4) to generate this AutoMigration.")
             );
         }
     }
@@ -85,18 +91,6 @@
         assertThat(autoMigrationDbV2.dao().getAllEntity1s().size(), is(1));
     }
 
-    // Run this to create the very 1st version of the db.
-    public void createFirstVersion() throws IOException {
-        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
-        db.close();
-    }
-
-    public void createSecondVersion() {
-        AutoMigrationDb autoMigrationDbV2 = getLatestDb();
-
-        assertThat(autoMigrationDbV2.dao().getAllEntity1s().size(), is(1));
-    }
-
     private AutoMigrationDb getLatestDb() {
         AutoMigrationDb db = Room.databaseBuilder(
                 InstrumentationRegistry.getInstrumentation().getTargetContext(),
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index d708dbf..003bb27 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -111,6 +111,7 @@
     method public void addMigrations(androidx.room.migration.Migration!...);
     method public void addMigrations(java.util.List<androidx.room.migration.Migration!>);
     method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+    method public java.util.Map<java.lang.Integer!,java.util.Map<java.lang.Integer!,androidx.room.migration.Migration!>!> getMigrations();
   }
 
   public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
diff --git a/room/runtime/api/public_plus_experimental_current.txt b/room/runtime/api/public_plus_experimental_current.txt
index 608cd54..fc91b21 100644
--- a/room/runtime/api/public_plus_experimental_current.txt
+++ b/room/runtime/api/public_plus_experimental_current.txt
@@ -116,6 +116,7 @@
     method public void addMigrations(androidx.room.migration.Migration!...);
     method public void addMigrations(java.util.List<androidx.room.migration.Migration!>);
     method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+    method public java.util.Map<java.lang.Integer!,java.util.Map<java.lang.Integer!,androidx.room.migration.Migration!>!> getMigrations();
   }
 
   public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 89994c2..7f8f611 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -154,6 +154,7 @@
     method public void addMigrations(androidx.room.migration.Migration!...);
     method public void addMigrations(java.util.List<androidx.room.migration.Migration!>);
     method public java.util.List<androidx.room.migration.Migration!>? findMigrationPath(int, int);
+    method public java.util.Map<java.lang.Integer!,java.util.Map<java.lang.Integer!,androidx.room.migration.Migration!>!> getMigrations();
   }
 
   public abstract static class RoomDatabase.PrepackagedDatabaseCallback {
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index f0d88eb..cd187e2 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -186,8 +186,12 @@
     public void init(@NonNull DatabaseConfiguration configuration) {
         mOpenHelper = createOpenHelper(configuration);
         List<Migration> autoMigrations = getAutoMigrations();
-        if (autoMigrations.size() > 0) {
-            configuration.migrationContainer.addMigrations(autoMigrations);
+        for (Migration autoMigration : autoMigrations) {
+            boolean migrationExists = configuration.migrationContainer.getMigrations()
+                            .containsKey(autoMigration.startVersion);
+            if (!migrationExists) {
+                configuration.migrationContainer.addMigrations(autoMigrations);
+            }
         }
 
         // Configure SqliteCopyOpenHelper if it is available:
@@ -261,13 +265,13 @@
         }
     }
 
-    @NonNull
+
     /**
-     * Returns a list of {@link Migration} of a database that have been generated using
-     * {@link AutoMigration}.
+     * Returns a list of {@link Migration} of a database that have been automatically generated.
      *
      * @return A list of migration instances each of which is a generated autoMigration
      */
+    @NonNull
     protected List<Migration> getAutoMigrations() {
         return Arrays.asList();
     }
@@ -1445,13 +1449,23 @@
             }
             Migration existing = targetMap.get(end);
             if (existing != null) {
-                // TODO: (b/182251019) Favor user specified migration over generated automigrations
                 Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
             }
             targetMap.put(end, migration);
         }
 
         /**
+         * Returns the map of available migrations where the key is the start version of the
+         * migration, and the value is a map of (end version -> Migration).
+         *
+         * @return Map of migrations keyed by the start version
+         */
+        @NonNull
+        public Map<Integer, Map<Integer, Migration>> getMigrations() {
+            return Collections.unmodifiableMap(mMigrations);
+        }
+
+        /**
          * Finds the list of migrations that should be run to move from {@code start} version to
          * {@code end} version.
          *