Supporting dependency injection in auto migrations.
Bug: 185235075
Test: Added test to use .addAutoMigrationSpec(...) in AutoMigrationTest.kt
Relnote: Updating the API to support dependency injection in auto migrations.
Change-Id: I100c8796ce18ebc1dddb3e46a00ef56d4f8f63f5
diff --git a/room/common/api/current.txt b/room/common/api/current.txt
index 63d6692..a7a1d84 100644
--- a/room/common/api/current.txt
+++ b/room/common/api/current.txt
@@ -166,6 +166,9 @@
method public abstract boolean autoGenerate() default false;
}
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+ }
+
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
}
diff --git a/room/common/api/public_plus_experimental_current.txt b/room/common/api/public_plus_experimental_current.txt
index 63d6692..a7a1d84 100644
--- a/room/common/api/public_plus_experimental_current.txt
+++ b/room/common/api/public_plus_experimental_current.txt
@@ -166,6 +166,9 @@
method public abstract boolean autoGenerate() default false;
}
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+ }
+
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
}
diff --git a/room/common/api/restricted_current.txt b/room/common/api/restricted_current.txt
index 30a6f37..220d387 100644
--- a/room/common/api/restricted_current.txt
+++ b/room/common/api/restricted_current.txt
@@ -166,6 +166,9 @@
method public abstract boolean autoGenerate() default false;
}
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedAutoMigrationSpec {
+ }
+
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface ProvidedTypeConverter {
}
diff --git a/room/common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.java b/room/common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.java
new file mode 100644
index 0000000..ee63b76
--- /dev/null
+++ b/room/common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as an auto migration spec that will be provided to Room at runtime.
+ * <p>
+ * An instance of a class annotated with this annotation has to be provided to Room using
+ * {@code Room.databaseBuilder.addAutoMigrationSpec(AutoMigrationSpec)}. Room will verify that
+ * the spec is provided in the builder configuration and if not, an
+ * {@link IllegalArgumentException} will be thrown.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface ProvidedAutoMigrationSpec {
+}
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 9d2bdc7..98837fc 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/AutoMigrationProcessor.kt
@@ -18,6 +18,7 @@
import androidx.room.DeleteColumn
import androidx.room.DeleteTable
+import androidx.room.ProvidedAutoMigrationSpec
import androidx.room.RenameColumn
import androidx.room.RenameTable
import androidx.room.compiler.processing.XType
@@ -50,19 +51,25 @@
* @return the AutoMigrationResult containing the schema changes detected
*/
fun process(): AutoMigrationResult? {
+ val isSpecProvided = spec.typeElement?.hasAnnotation(
+ ProvidedAutoMigrationSpec::class
+ ) ?: false
val specElement = if (!spec.isTypeOf(Any::class)) {
val typeElement = spec.typeElement
+
if (typeElement == null || typeElement.isInterface() || typeElement.isAbstract()) {
context.logger.e(element, AUTOMIGRATION_SPEC_MUST_BE_CLASS)
return null
}
- val constructors = element.getConstructors()
- context.checker.check(
- constructors.isEmpty() || constructors.any { it.parameters.isEmpty() },
- element,
- ProcessorErrors.AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR
- )
+ if (!isSpecProvided) {
+ val constructors = element.getConstructors()
+ context.checker.check(
+ constructors.isEmpty() || constructors.any { it.parameters.isEmpty() },
+ element,
+ ProcessorErrors.AUTOMIGRATION_SPEC_MISSING_NOARG_CONSTRUCTOR
+ )
+ }
context.checker.check(
typeElement.enclosingTypeElement == null || typeElement.isStatic(),
@@ -163,11 +170,12 @@
from = fromSchemaBundle.version,
to = toSchemaBundle.version,
schemaDiff = schemaDiff,
- specElement = specElement
+ specElement = specElement,
+ isSpecProvided = isSpecProvided
)
}
- // TODO: (b/180389433) Verify automigration schemas before calling the AutoMigrationProcessor
+ // TODO: (b/180389433) Verify auto migration schemas before calling the AutoMigrationProcessor
private fun getValidatedSchemaFile(version: Int): File? {
val schemaFile = File(
context.schemaOutFolder,
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 fc2a5f7..8166475 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/AutoMigrationResult.kt
@@ -30,12 +30,13 @@
val from: Int?,
val to: Int?,
val specElement: XTypeElement?,
- val schemaDiff: SchemaDiffResult
+ val schemaDiff: SchemaDiffResult,
+ val isSpecProvided: Boolean
) {
val implTypeName: ClassName by lazy {
ClassName.get(
element.className.packageName(),
- "AutoMigration_${from}_${to}_Impl"
+ "${element.className.simpleName()}_AutoMigration_${from}_${to}_Impl"
)
}
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 d62073e..0f9efb9 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -54,16 +54,18 @@
superclass(RoomTypeNames.MIGRATION)
if (autoMigrationResult.specClassName != null) {
- builder.addField(
+ val callbackField =
FieldSpec.builder(
- autoMigrationResult.specClassName,
+ RoomTypeNames.AUTO_MIGRATION_SPEC,
"callback",
Modifier.PRIVATE,
Modifier.FINAL
- ).initializer(
- "new $T()", autoMigrationResult.specClassName
- ).build()
- )
+ ).apply {
+ if (!autoMigrationResult.isSpecProvided) {
+ initializer("new $T()", autoMigrationResult.specClassName)
+ }
+ }
+ builder.addField(callbackField.build())
}
addMethod(createConstructor())
addMethod(createMigrateMethod())
@@ -80,6 +82,15 @@
return MethodSpec.constructorBuilder().apply {
addModifiers(Modifier.PUBLIC)
addStatement("super($L, $L)", autoMigrationResult.from, autoMigrationResult.to)
+ if (autoMigrationResult.isSpecProvided) {
+ addParameter(
+ ParameterSpec.builder(
+ RoomTypeNames.AUTO_MIGRATION_SPEC,
+ "callback"
+ ).addAnnotation(NonNull::class.java).build()
+ )
+ addStatement("this.callback = callback")
+ }
}.build()
}
@@ -111,6 +122,8 @@
* @param migrateBuilder Builder for the migrate() function to be generated
*/
private fun addAutoMigrationResultToMigrate(migrateBuilder: MethodSpec.Builder) {
+ // TODO: (b/185934598) Handle views here, the order in which the views themselves are
+ // recreated is important
addSimpleChangeStatements(migrateBuilder)
addComplexChangeStatements(migrateBuilder)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
index a8ac300..1f75228 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
@@ -64,6 +64,7 @@
addMethod(createCreateInvalidationTracker())
addMethod(createClearAllTables())
addMethod(createCreateTypeConvertersMap())
+ addMethod(createCreateAutoMigrationSpecsSet())
addMethod(getAutoMigrations())
}
addDaoImpls(builder)
@@ -125,6 +126,47 @@
}.build()
}
+ private fun createCreateAutoMigrationSpecsSet(): MethodSpec {
+ val scope = CodeGenScope(this)
+ return MethodSpec.methodBuilder("getRequiredAutoMigrationSpecs").apply {
+ addAnnotation(Override::class.java)
+ addModifiers(PROTECTED)
+ returns(
+ ParameterizedTypeName.get(
+ CommonTypeNames.SET,
+ ParameterizedTypeName.get(
+ ClassName.get(Class::class.java),
+ WildcardTypeName.subtypeOf(RoomTypeNames.AUTO_MIGRATION_SPEC)
+ )
+ )
+ )
+ val autoMigrationSpecsVar = scope.getTmpVar("_autoMigrationSpecsSet")
+ val autoMigrationSpecsTypeName = ParameterizedTypeName.get(
+ ClassName.get(HashSet::class.java),
+ ParameterizedTypeName.get(
+ ClassName.get(Class::class.java),
+ WildcardTypeName.subtypeOf(RoomTypeNames.AUTO_MIGRATION_SPEC)
+ )
+ )
+ addStatement(
+ "final $T $L = new $T()",
+ autoMigrationSpecsTypeName,
+ autoMigrationSpecsVar,
+ autoMigrationSpecsTypeName
+ )
+ database.autoMigrations.map { autoMigrationResult ->
+ if (autoMigrationResult.isSpecProvided) {
+ addStatement(
+ "$L.add($T.class)",
+ autoMigrationSpecsVar,
+ autoMigrationResult.specClassName
+ )
+ }
+ }
+ addStatement("return $L", autoMigrationSpecsVar)
+ }.build()
+ }
+
private fun createClearAllTables(): MethodSpec {
val scope = CodeGenScope(this)
return MethodSpec.methodBuilder("clearAllTables").apply {
@@ -310,8 +352,16 @@
addModifiers(PROTECTED)
addAnnotation(Override::class.java)
returns(ParameterizedTypeName.get(CommonTypeNames.LIST, RoomTypeNames.MIGRATION))
- val autoMigrationsList = database.autoMigrations.map {
- CodeBlock.of("new $T()", it.implTypeName)
+ val autoMigrationsList = database.autoMigrations.map { autoMigrationResult ->
+ if (autoMigrationResult.isSpecProvided) {
+ CodeBlock.of(
+ "new $T(mAutoMigrationSpecs.get($T.class))",
+ autoMigrationResult.implTypeName,
+ autoMigrationResult.specClassName
+ )
+ } else {
+ CodeBlock.of("new $T()", autoMigrationResult.implTypeName)
+ }
}
addStatement(
"return $T.asList($L)",
diff --git a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
index ba487c3..530c21b 100644
--- a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
+++ b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithDefault.java
@@ -1,6 +1,7 @@
package foo.bar;
import androidx.annotation.NonNull;
+import androidx.room.migration.AutoMigrationSpec;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import java.lang.Override;
@@ -9,10 +10,10 @@
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation"})
-class AutoMigration_1_2_Impl extends Migration {
- private final ValidAutoMigrationWithDefault callback = new ValidAutoMigrationWithDefault();
+class ValidAutoMigrationWithDefault_AutoMigration_1_2_Impl extends Migration {
+ private final AutoMigrationSpec callback = new ValidAutoMigrationWithDefault();
- public AutoMigration_1_2_Impl() {
+ public ValidAutoMigrationWithDefault_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 0a65d6f..b8ab84c 100644
--- a/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
+++ b/room/compiler/src/test/data/autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java
@@ -1,6 +1,7 @@
package foo.bar;
import androidx.annotation.NonNull;
+import androidx.room.migration.AutoMigrationSpec;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import java.lang.Override;
@@ -9,10 +10,10 @@
@Generated("androidx.room.RoomProcessor")
@SuppressWarnings({"unchecked", "deprecation"})
-class AutoMigration_1_2_Impl extends Migration {
- private final ValidAutoMigrationWithoutDefault callback = new ValidAutoMigrationWithoutDefault();
+class ValidAutoMigrationWithoutDefault_AutoMigration_1_2_Impl extends Migration {
+ private final AutoMigrationSpec callback = new ValidAutoMigrationWithoutDefault();
- public AutoMigration_1_2_Impl() {
+ public ValidAutoMigrationWithoutDefault_AutoMigration_1_2_Impl() {
super(1, 2);
}
diff --git a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
index 86359d5..5dd84db 100644
--- a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
+++ b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
@@ -5,6 +5,7 @@
import androidx.room.RoomOpenHelper;
import androidx.room.RoomOpenHelper.Delegate;
import androidx.room.RoomOpenHelper.ValidationResult;
+import androidx.room.migration.AutoMigrationSpec;
import androidx.room.migration.Migration;
import androidx.room.util.DBUtil;
import androidx.room.util.TableInfo;
@@ -187,6 +188,12 @@
}
@Override
+ protected Set<Class<? extends AutoMigrationSpec>> getRequiredAutoMigrationSpecs() {
+ final HashSet<Class<? extends AutoMigrationSpec>> _autoMigrationSpecsSet = new HashSet<Class<? extends AutoMigrationSpec>>();
+ return _autoMigrationSpecsSet;
+ }
+
+ @Override
protected List<Migration> getAutoMigrations() {
return Arrays.asList();
}
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 a7d0fc0e..57203fde 100644
--- a/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/AutoMigrationWriterTest.kt
@@ -73,6 +73,7 @@
specElement = invocation.processingEnv.requireTypeElement(
"foo.bar.ValidAutoMigrationWithDefault"
),
+ isSpecProvided = false
)
AutoMigrationWriter(
autoMigrationResultWithNewAddedColumn.element,
@@ -85,7 +86,7 @@
loadTestSource(
"autoMigrationWriter/output/ValidAutoMigrationWithDefault" +
".java",
- "foo.bar.AutoMigration_1_2_Impl"
+ "foo.bar.ValidAutoMigrationWithDefault_AutoMigration_1_2_Impl"
)
)
}
@@ -136,6 +137,7 @@
specElement = invocation.processingEnv.requireTypeElement(
"foo.bar.ValidAutoMigrationWithoutDefault"
),
+ isSpecProvided = false
)
AutoMigrationWriter(
autoMigrationResultWithNewAddedColumn.element,
@@ -147,7 +149,7 @@
generatedSource(
loadTestSource(
"autoMigrationWriter/output/ValidAutoMigrationWithoutDefault.java",
- "foo.bar.AutoMigration_1_2_Impl"
+ "foo.bar.ValidAutoMigrationWithoutDefault_AutoMigration_1_2_Impl"
)
)
}
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
new file mode 100644
index 0000000..a47d59f
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
@@ -0,0 +1,47 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "250b199947a14a67da3f70823e90f8bc",
+ "entities": [
+ {
+ "tableName": "Entity1",
+ "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": [],
+ "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, '250b199947a14a67da3f70823e90f8bc')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
new file mode 100644
index 0000000..96cda7c
--- /dev/null
+++ b/room/integration-tests/testapp/schemas/androidx.room.integration.testapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
@@ -0,0 +1,87 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "553bb9e0bb6c2673f0687f76e66f6304",
+ "entities": [
+ {
+ "tableName": "Entity1",
+ "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": "Entity2",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "addedInV1",
+ "columnName": "addedInV1",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "1"
+ },
+ {
+ "fieldPath": "addedInV2",
+ "columnName": "addedInV2",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "2"
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "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, '553bb9e0bb6c2673f0687f76e66f6304')"
+ ]
+ }
+}
\ 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 997a9d1..64796f1 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
@@ -16,9 +16,6 @@
package androidx.room.integration.testapp.migration;
-
-import android.database.sqlite.SQLiteDatabase;
-
import androidx.annotation.NonNull;
import androidx.room.AutoMigration;
import androidx.room.ColumnInfo;
@@ -38,6 +35,7 @@
import androidx.room.RenameTable;
import androidx.room.RoomDatabase;
import androidx.room.migration.AutoMigrationSpec;
+import androidx.sqlite.db.SupportSQLiteDatabase;
import java.util.List;
@@ -413,7 +411,8 @@
toColumnName = "renamedInV2")
@DeleteColumn(tableName = "Entity15", columnName = "addedInV1")
static class SimpleAutoMigration1 implements AutoMigrationSpec {
- void onPostMigrate(SQLiteDatabase db) {
+ @Override
+ public void onPostMigrate(@NonNull SupportSQLiteDatabase db) {
// Do something
}
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/ProvidedAutoMigrationSpecTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/ProvidedAutoMigrationSpecTest.java
new file mode 100644
index 0000000..da5c158
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/ProvidedAutoMigrationSpecTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.integration.testapp.migration;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import androidx.annotation.NonNull;
+import androidx.room.AutoMigration;
+import androidx.room.ColumnInfo;
+import androidx.room.Database;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+import androidx.room.ProvidedAutoMigrationSpec;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.migration.AutoMigrationSpec;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Test custom database migrations.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ProvidedAutoMigrationSpecTest {
+ private static final String TEST_DB = "auto-migration-test";
+ private ProvidedAutoMigrationDb.MyProvidedAutoMigration mProvidedSpec =
+ new ProvidedAutoMigrationDb.MyProvidedAutoMigration("Hi");
+
+ @Rule
+ public MigrationTestHelper helper;
+
+ public ProvidedAutoMigrationSpecTest() {
+ helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
+ ProvidedAutoMigrationDb.class.getCanonicalName());
+ }
+
+ @Database(
+ version = ProvidedAutoMigrationDb.LATEST_VERSION,
+ entities = {
+ ProvidedAutoMigrationDb.Entity1.class,
+ ProvidedAutoMigrationDb.Entity2.class
+ },
+ autoMigrations = {
+ @AutoMigration(
+ from = 1, to = 2, spec =
+ ProvidedAutoMigrationDb.MyProvidedAutoMigration.class
+ )
+ },
+ exportSchema = true
+ )
+ public abstract static class ProvidedAutoMigrationDb extends RoomDatabase {
+ static final int LATEST_VERSION = 2;
+
+ /**
+ * No change between versions.
+ */
+ @Entity
+ static class Entity1 {
+ public static final String TABLE_NAME = "Entity1";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ }
+
+ /**
+ * A new table added.
+ */
+ @Entity
+ static class Entity2 {
+ public static final String TABLE_NAME = "Entity2";
+ @PrimaryKey
+ public int id;
+ public String name;
+ @ColumnInfo(defaultValue = "1")
+ public int addedInV1;
+ @ColumnInfo(defaultValue = "2")
+ public int addedInV2;
+ }
+
+ @ProvidedAutoMigrationSpec
+ static class MyProvidedAutoMigration implements AutoMigrationSpec {
+ private final String mPrefString;
+ public boolean mOnPostMigrateCalled = false;
+
+ MyProvidedAutoMigration(String prefString) {
+ this.mPrefString = prefString;
+ }
+
+ @Override
+ public void onPostMigrate(@NonNull SupportSQLiteDatabase db) {
+ mOnPostMigrateCalled = true;
+ }
+ }
+ }
+
+ // 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 testOnPostMigrate() throws IOException {
+ SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
+ ProvidedAutoMigrationDb autoMigrationDbV2 = getLatestDb();
+ helper.runMigrationsAndValidate(
+ TEST_DB,
+ 2,
+ true
+ );
+ assertThat(mProvidedSpec.mOnPostMigrateCalled, is(true));
+ }
+
+ /**
+ * Verifies that the user defined migration is selected over using an autoMigration.
+ */
+ @Test
+ public void testNoSpecProvidedInConfig() {
+ try {
+ ProvidedAutoMigrationDb autoMigrationDbV2 = Room.databaseBuilder(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ ProvidedAutoMigrationDb.class, TEST_DB).build();
+ } catch (IllegalArgumentException exception) {
+ assertThat(
+ exception.getMessage(),
+ containsString(
+ "A required auto migration spec (androidx.room.integration.testapp"
+ + ".migration.ProvidedAutoMigrationSpecTest"
+ + ".ProvidedAutoMigrationDb.MyProvidedAutoMigration) is "
+ + "missing in the database configuration."
+ )
+ );
+ }
+ }
+
+ private ProvidedAutoMigrationDb getLatestDb() {
+ ProvidedAutoMigrationDb db = Room.databaseBuilder(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ ProvidedAutoMigrationDb.class, TEST_DB)
+ .addAutoMigrationSpec(mProvidedSpec)
+ .build();
+ db.getOpenHelper().getWritableDatabase(); // trigger open
+ helper.closeWhenFinished(db);
+ return db;
+ }
+}
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index e093582..50f212f 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -6,6 +6,7 @@
method @Deprecated public boolean isMigrationRequiredFrom(int);
field public final boolean allowDestructiveMigrationOnDowngrade;
field public final boolean allowMainThreadQueries;
+ field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
field public final android.content.Context context;
field public final String? copyFromAssetPath;
@@ -71,6 +72,7 @@
}
public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+ method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
diff --git a/room/runtime/api/public_plus_experimental_current.txt b/room/runtime/api/public_plus_experimental_current.txt
index ec1cb85..758a9f6 100644
--- a/room/runtime/api/public_plus_experimental_current.txt
+++ b/room/runtime/api/public_plus_experimental_current.txt
@@ -6,6 +6,7 @@
method @Deprecated public boolean isMigrationRequiredFrom(int);
field public final boolean allowDestructiveMigrationOnDowngrade;
field public final boolean allowMainThreadQueries;
+ field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
field public final android.content.Context context;
field public final String? copyFromAssetPath;
@@ -75,6 +76,7 @@
}
public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+ method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 8075022..14c301a 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -7,11 +7,13 @@
ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?);
ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?);
ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback!>?, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer!>?, String?, java.io.File?, java.util.concurrent.Callable<java.io.InputStream!>?, androidx.room.RoomDatabase.PrepackagedDatabaseCallback?, java.util.List<java.lang.Object!>?, java.util.List<androidx.room.migration.AutoMigrationSpec!>?);
method public boolean isMigrationRequired(int, int);
method @Deprecated public boolean isMigrationRequiredFrom(int);
field public final boolean allowDestructiveMigrationOnDowngrade;
field public final boolean allowMainThreadQueries;
+ field public final java.util.List<androidx.room.migration.AutoMigrationSpec!> autoMigrationSpecs;
field public final java.util.List<androidx.room.RoomDatabase.Callback!>? callbacks;
field public final android.content.Context context;
field public final String? copyFromAssetPath;
@@ -114,6 +116,7 @@
}
public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+ method public androidx.room.RoomDatabase.Builder<T!> addAutoMigrationSpec(androidx.room.migration.AutoMigrationSpec);
method public androidx.room.RoomDatabase.Builder<T!> addCallback(androidx.room.RoomDatabase.Callback);
method public androidx.room.RoomDatabase.Builder<T!> addMigrations(androidx.room.migration.Migration!...);
method public androidx.room.RoomDatabase.Builder<T!> addTypeConverter(Object);
diff --git a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
index d19c0cb..1861836 100644
--- a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
+++ b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.room.migration.AutoMigrationSpec;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import java.io.File;
@@ -69,6 +70,9 @@
@NonNull
public final List<Object> typeConverters;
+ @NonNull
+ public final List<AutoMigrationSpec> autoMigrationSpecs;
+
/**
* Whether Room should throw an exception for queries run on the main thread.
*/
@@ -133,14 +137,13 @@
@Nullable
public final Callable<InputStream> copyFromInputStream;
-
/**
* Creates a database configuration with the given values.
*
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -170,7 +173,8 @@
@Nullable Set<Integer> migrationNotRequiredFrom) {
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, queryExecutor, false,
- requireMigration, false, migrationNotRequiredFrom, null, null, null, null, null);
+ requireMigration, false, migrationNotRequiredFrom, null, null, null, null, null,
+ null);
}
/**
@@ -179,7 +183,7 @@
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -216,7 +220,7 @@
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
- migrationNotRequiredFrom, null, null, null, null, null);
+ migrationNotRequiredFrom, null, null, null, null, null, null);
}
/**
@@ -225,7 +229,7 @@
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -266,7 +270,7 @@
this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
- migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, null, null, null);
+ migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, null, null, null, null);
}
/**
@@ -275,7 +279,7 @@
* @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -320,16 +324,16 @@
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
- null, null);
+ null, null, null);
}
- /**
- * Creates a database configuration with the given values.
- *
- * @deprecated Use {@link #DatabaseConfiguration(Context, String,
+ /**
+ * Creates a database configuration with the given values.
+ *
+ * @deprecated Use {@link #DatabaseConfiguration(Context, String,
* SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
* RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
- * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>)}
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
*
* @param context The application context.
* @param name Name of the database, can be null if it is in memory.
@@ -377,7 +381,66 @@
allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
- prepackagedDatabaseCallback, null);
+ prepackagedDatabaseCallback, null, null);
+ }
+
+ /**
+ * Creates a database configuration with the given values.
+ *
+ * @deprecated Use {@link #DatabaseConfiguration(Context, String,
+ * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean,
+ * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File,
+ * Callable, RoomDatabase.PrepackagedDatabaseCallback, List<Object>, List<AutoMigrationSpec>)}
+ *
+ * @param context The application context.
+ * @param name Name of the database, can be null if it is in memory.
+ * @param sqliteOpenHelperFactory The open helper factory to use.
+ * @param migrationContainer The migration container for migrations.
+ * @param callbacks The list of callbacks for database events.
+ * @param allowMainThreadQueries Whether to allow main thread reads/writes or not.
+ * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING.
+ * @param queryExecutor The Executor used to execute asynchronous queries.
+ * @param transactionExecutor The Executor used to execute asynchronous transactions.
+ * @param multiInstanceInvalidation True if Room should perform multi-instance invalidation.
+ * @param requireMigration True if Room should require a valid migration if version changes,
+ * @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no
+ * migration is supplied during a downgrade.
+ * @param migrationNotRequiredFrom The collection of schema versions from which migrations
+ * aren't required.
+ * @param copyFromAssetPath The assets path to the pre-packaged database.
+ * @param copyFromFile The pre-packaged database file.
+ * @param copyFromInputStream The callable to get the input stream from which a
+ * pre-package database file will be copied from.
+ * @param prepackagedDatabaseCallback The pre-packaged callback.
+ * @param typeConverters The type converters.
+ *
+ * @hide
+ */
+ @Deprecated
+ @SuppressLint("LambdaLast")
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public DatabaseConfiguration(@NonNull Context context, @Nullable String name,
+ @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory,
+ @NonNull RoomDatabase.MigrationContainer migrationContainer,
+ @Nullable List<RoomDatabase.Callback> callbacks,
+ boolean allowMainThreadQueries,
+ @NonNull RoomDatabase.JournalMode journalMode,
+ @NonNull Executor queryExecutor,
+ @NonNull Executor transactionExecutor,
+ boolean multiInstanceInvalidation,
+ boolean requireMigration,
+ boolean allowDestructiveMigrationOnDowngrade,
+ @Nullable Set<Integer> migrationNotRequiredFrom,
+ @Nullable String copyFromAssetPath,
+ @Nullable File copyFromFile,
+ @Nullable Callable<InputStream> copyFromInputStream,
+ @Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback,
+ @Nullable List<Object> typeConverters) {
+ this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks,
+ allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor,
+ multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade,
+ migrationNotRequiredFrom, copyFromAssetPath, copyFromFile, copyFromInputStream,
+ prepackagedDatabaseCallback, typeConverters, null);
}
/**
@@ -404,6 +467,7 @@
* pre-package database file will be copied from.
* @param prepackagedDatabaseCallback The pre-packaged callback.
* @param typeConverters The type converters.
+ * @param autoMigrationSpecs The auto migration specs.
*
* @hide
*/
@@ -425,7 +489,8 @@
@Nullable File copyFromFile,
@Nullable Callable<InputStream> copyFromInputStream,
@Nullable RoomDatabase.PrepackagedDatabaseCallback prepackagedDatabaseCallback,
- @Nullable List<Object> typeConverters) {
+ @Nullable List<Object> typeConverters,
+ @Nullable List<AutoMigrationSpec> autoMigrationSpecs) {
this.sqliteOpenHelperFactory = sqliteOpenHelperFactory;
this.context = context;
this.name = name;
@@ -444,6 +509,8 @@
this.copyFromInputStream = copyFromInputStream;
this.prepackagedDatabaseCallback = prepackagedDatabaseCallback;
this.typeConverters = typeConverters == null ? Collections.emptyList() : typeConverters;
+ this.autoMigrationSpecs = autoMigrationSpecs == null
+ ? Collections.emptyList() : autoMigrationSpecs;
}
/**
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index 2dfbf93..f08ff2b 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -33,6 +33,7 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.WorkerThread;
import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.room.migration.AutoMigrationSpec;
import androidx.room.migration.Migration;
import androidx.room.util.SneakyThrow;
import androidx.sqlite.db.SimpleSQLiteQuery;
@@ -100,6 +101,15 @@
@Deprecated
protected List<Callback> mCallbacks;
+ /**
+ * A map of auto migration spec classes to their provided instance.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @NonNull
+ protected Map<Class<? extends AutoMigrationSpec>, AutoMigrationSpec> mAutoMigrationSpecs;
+
private final ReentrantReadWriteLock mCloseLock = new ReentrantReadWriteLock();
@Nullable
@@ -151,7 +161,6 @@
// Updated later to an unmodifiable map when init is called.
private final Map<Class<?>, Object> mTypeConverters;
-
/**
* Gets the instance of the given Type Converter.
*
@@ -175,6 +184,7 @@
public RoomDatabase() {
mInvalidationTracker = createInvalidationTracker();
mTypeConverters = new HashMap<>();
+ mAutoMigrationSpecs = new HashMap<>();
}
/**
@@ -185,6 +195,39 @@
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {
mOpenHelper = createOpenHelper(configuration);
+ Set<Class<? extends AutoMigrationSpec>> requiredAutoMigrationSpecs =
+ getRequiredAutoMigrationSpecs();
+ BitSet usedSpecs = new BitSet();
+ for (Class<? extends AutoMigrationSpec> spec : requiredAutoMigrationSpecs) {
+ int foundIndex = -1;
+ for (int providedIndex = configuration.autoMigrationSpecs.size() - 1;
+ providedIndex >= 0; providedIndex--
+ ) {
+ Object provided = configuration.autoMigrationSpecs.get(providedIndex);
+ if (spec.isAssignableFrom(provided.getClass())) {
+ foundIndex = providedIndex;
+ usedSpecs.set(foundIndex);
+ break;
+ }
+ }
+ if (foundIndex < 0) {
+ throw new IllegalArgumentException(
+ "A required auto migration spec (" + spec.getCanonicalName()
+ + ") is missing in the database configuration.");
+ }
+ mAutoMigrationSpecs.put(spec, configuration.autoMigrationSpecs.get(foundIndex));
+ }
+
+ for (int providedIndex = configuration.autoMigrationSpecs.size() - 1;
+ providedIndex >= 0; providedIndex--) {
+ if (!usedSpecs.get(providedIndex)) {
+ throw new IllegalArgumentException("Unexpected auto migration specs found. "
+ + "Annotate AutoMigrationSpec implementation with "
+ + "@ProvidedAutoMigrationSpec annotation or remove this spec from the "
+ + "builder.");
+ }
+ }
+
List<Migration> autoMigrations = getAutoMigrations();
for (Migration autoMigration : autoMigrations) {
boolean migrationExists = configuration.migrationContainer.getMigrations()
@@ -376,6 +419,21 @@
}
/**
+ * Returns a Set of required AutoMigrationSpec classes.
+ * <p>
+ * This is implemented by the generated code.
+ *
+ * @return Creates a set that will include all required auto migration specs for this database.
+ *
+ * @hide
+ */
+ @NonNull
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ protected Set<Class<? extends AutoMigrationSpec>> getRequiredAutoMigrationSpecs() {
+ return Collections.emptySet();
+ }
+
+ /**
* Deletes all rows from all the tables that are registered to this database as
* {@link Database#entities()}.
* <p>
@@ -743,6 +801,7 @@
private QueryCallback mQueryCallback;
private Executor mQueryCallbackExecutor;
private List<Object> mTypeConverters;
+ private List<AutoMigrationSpec> mAutoMigrationSpecs;
/** The Executor used to run database queries. This should be background-threaded. */
private Executor mQueryExecutor;
@@ -1015,6 +1074,23 @@
}
/**
+ * Adds an auto migration spec to the builder.
+ *
+ * @param autoMigrationSpec The auto migration object that is annotated with
+ * {@link AutoMigrationSpec} and is declared in an {@link AutoMigration} annotation.
+ * @return This {@link Builder} instance.
+ */
+ @NonNull
+ @SuppressWarnings("MissingGetterMatchingBuilder")
+ public Builder<T> addAutoMigrationSpec(@NonNull AutoMigrationSpec autoMigrationSpec) {
+ if (mAutoMigrationSpecs == null) {
+ mAutoMigrationSpecs = new ArrayList<>();
+ }
+ mAutoMigrationSpecs.add(autoMigrationSpec);
+ return this;
+ }
+
+ /**
* Disables the main thread query check for Room.
* <p>
* Room ensures that Database is never accessed on the main thread because it may lock the
@@ -1407,7 +1483,8 @@
mCopyFromFile,
mCopyFromInputStream,
mPrepackagedDatabaseCallback,
- mTypeConverters);
+ mTypeConverters,
+ mAutoMigrationSpecs);
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);
return db;
diff --git a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
index fcb1b87..c846037 100644
--- a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
+++ b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
@@ -169,6 +169,7 @@
null,
null,
null,
+ null,
null);
RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
new CreatingDelegate(schemaBundle.getDatabase()),
@@ -232,6 +233,7 @@
null,
null,
null,
+ null,
null);
RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),