【发布时间】:2021-05-28 03:28:57
【问题描述】:
我有一个用例,我需要将 schema1.db 中的 token 表中的所有行内容复制到 schema2.db 中的另一个表 token。我们正在使用 Android Room 来满足我们的数据库需求。
// Migration task for the database schema2.db
class MyMigration_From_4_To_5(private val context: Context): Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"ATTACH DATABASE 'db1.db' as 'db1'")
database.execSQL("INSERT INTO token(userId, deviceToken) SELECT userId, deviceToken FROM db1.token")
database.execSQL("DETACH DATABASE 'db1'")
}
}
我的测试如下
class MyMigration_From_4_To_5Test {
@get:Rule
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
MyDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
@Test
@Throws(Exception::class)
fun testMigrationCreatesTable() {
val migration = MyMigration_From_4_To_5(InstrumentationRegistry.getInstrumentation().context)
helper.createDatabase(MyDatabase.NAME, migration.startVersion).use {
MatcherAssert.assertThat(
it,
Matchers.whenQueried("PRAGMA table_info(token)", Matchers.rowCount(CoreMatchers.`is`(2)))
)
}
helper.createDatabase(MyDatabase.NAME, migration.endVersion).use {
MatcherAssert.assertThat(
it,
Matchers.whenQueried(
"SELECT userId, deviceToken FROM token",
Matchers.rowCount(org.hamcrest.Matchers.greaterThan(0)) // Hamcrest type safe matcher which accepts cursor, and verifies the number of rows returned.
)
)
}
}
}
运行测试时出现以下错误
android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database: file:/data/user/0/com.myproject.db.test/databases/token (code 14 SQLITE_CANTOPEN)
at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
和
android.database.sqlite.SQLiteException: no such table: db1.token (code 1 SQLITE_ERROR[1]): , while compiling: INSERT INTO main.token(userId, deviceToken) SELECT userId, deviceToken FROM db1.token
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
和
com.myproject.MyMigration_From_4_To_5Test > testMigrationCreatesTable[SM-G975U1 - 11] [31mFAILED [0m
java.lang.AssertionError:
Expected: query 'SELECT userId, deviceToken FROM token' matches: row count a value greater than <0>
我也尝试了另一种方法
override fun migrate(database: SupportSQLiteDatabase) {
val sourceDatabasePath: File = context.getDatabasePath("db1")
database.execSQL("ATTACH DATABASE '$sourceDatabasePath' AS 'db1'")
database.execSQL("INSERT INTO main.token(userId, deviceToken) SELECT userId, deviceToken FROM db1.token")
database.execSQL("DETACH DATABASE 'db1'")
}
我为此使用了sqlite's open draft syntax。但我得到了同样的错误。
表db1.token 存在于我的设备上,但我无法将数据从db1.token 复制到db2.token。如何将数据从db1.token 复制到db2.token?
更新 #2
我尝试了另一种方法,使用 SQLiteOpenHelper 创建数据库助手并以这种方式查询表。
/**
* Helper class to query the existing `token` database
*/
class TokenMigrationHelper(context: Context): SQLiteOpenHelper(context, TOKEN_DB_NAME, null, TOKEN_DB_VERSION) {
companion object {
const val TOKEN_DB_NAME = "db1.db" // Existing database name
const val TOKEN_DB_VERSION = 6 // Token's database version
}
override fun onCreate(db: SQLiteDatabase?) {
// Already created.
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
// No need for an upgrade
}
}
迁移任务更新
class MyMigration_From_4_To_5 @Inject constructor(
private val context: Context
): Migration(
4,
5
) {
override fun migrate(database: SupportSQLiteDatabase) {
val tokenMigrationHelper = TokenMigrationHelper(context)
val tokenDatabase: SQLiteDatabase = tokenMigrationHelper.readableDatabase
with(tokenDatabase) {
var cursor: Cursor? = null
try {
cursor = rawQuery("SELECT name, value FROM `${tokenMigrationHelper.databaseName}.token`", null)
if (cursor.moveToFirst()) {
do {
database.execSQL(
"INSERT INTO `token` (hashedUserId, deviceToken) VALUES(?, ?)",
arrayOf(cursor.getString(0), cursor.getString(1))
)
} while (cursor.moveToNext())
}
} finally {
cursor?.close()
}
}
}
}
更新测试
@Test
fun testMigration__DataIsCopied() {
val migration = MyMigration_From_4_To_5(InstrumentationRegistry.getInstrumentation().context)
// Verify that the table already exists
helper.createDatabase(EbayDatabase.NAME, migration.startVersion).use {
MatcherAssert.assertThat(
it,
Matchers.whenQueried("PRAGMA table_info(token)", Matchers.rowCount(CoreMatchers.`is`(2)))
)
}
// Verify that table has not been updated
helper.createDatabase(EbayDatabase.NAME, migration.endVersion).use {
MatcherAssert.assertThat(
it,
Matchers.whenQueried("PRAGMA table_info(token)", Matchers.rowCount(CoreMatchers.`is`(2)))
)
}
// Verify that data has been inserted by the migration task
helper.runMigrationsAndValidate(EbayDatabase.NAME, migration.endVersion, true, migration).use {
it.execSQL("SELECT hashedUserId, deviceToken FROM token")
MatcherAssert.assertThat(
it,
Matchers.whenQueried(
"SELECT hashedUserId, deviceToken FROM token",
Matchers.rowCount(org.hamcrest.Matchers.greaterThan(0))
)
)
}
}
我收到以下错误
android.database.sqlite.SQLiteException: no such table: db1.db.token (code 1 SQLITE_ERROR): , while compiling: SELECT name, value FROM `db1.db.token`
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
参考资料:
【问题讨论】:
-
顺便说一句,您能检查一下您的示例代码是否正确,您可以使用上下文构建迁移:
MyMigration_From_4_To_5(InstrumentationRegistry.getInstrumentation().context)但是带有实际迁移的代码 sn-p 没有这样的构造函数。 -
我已经更新了代码。我在格式化代码时不小心删除了构造函数。现在是正确的。
标签: android kotlin android-sqlite android-room database-migration