无法从数据库中的 table 复制数据并将其复制到具有 Android 空间的另一个数据库中的另一个 table
Unable to copy data from a table in a database and copy it to another table in another database with Android room
我有一个用例,我需要将所有行内容从 token
table in schema1.db 复制到另一个 table token
in schema2.db。我们正在使用 Android 空间来满足我们的数据库要求。
// 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。但是我得到了同样的错误。
我的设备上存在 table db1.token
,但我无法将数据从 db1.token
复制到 db2.token
。如何将数据从 db1.token
复制到 db2.token
?
更新#2
我尝试了另一种方法,其中我使用 SQLiteOpenHelper 创建了一个数据库助手并以这种方式查询 table。
/**
* 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)
参考文献:
您需要使用 'target context',即 InstrumentationRegistry.getInstrumentation().targetContext
作为打开数据库的上下文。
在错误中:
unable to open database: file:/data/user/0/com.myproject.db.test/databases/token
请注意,正在使用的应用程序包以 test
结尾,这是因为使用的上下文来自测试应用程序,而不是应用程序 'under test'。
我让它按如下方式工作
第 1 步:创建 SqLiteOpenHelper
的自定义实例
class DataMigrationHelper @Inject constructor(
context: Context
): SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase?) {
// Already created.
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
// No need for an upgrade
}
companion object {
const val DB_NAME = "item_cache.db"
const val DB_VERSION = 19
}
}
第 2 步:将此实例传递给迁移任务
class MyMigration_From_4_To_5 @Inject constructor(
private val migrationHelper: DataMigrationHelper
): Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
val migrationDatabase: SQLiteDatabase = migrationHelper.readableDatabase
val nameValueList: MutableList<Array<Any>> = mutableListOf()
with(migrationDatabase) {
var cursor: Cursor? = null
try {
cursor = rawQuery("SELECT userId, deviceToken FROM token", null)
if (cursor.moveToFirst()) {
do {
nameValueList.add(arrayOf(cursor.getString(0), cursor.getString(1)))
} while (cursor.moveToNext())
} else {
// To get around the compilation error
// 'if' must have both main and 'else' branches if used as an expression
}
} catch (e: SQLiteException) {
// Swallow the exception.
} finally {
cursor?.close()
}
}
nameValueList.forEach {
database.execSQL(
"INSERT INTO `token`(userId, deviceToken) VALUES(?, ?)",
it
)
}
}
}
我有一个用例,我需要将所有行内容从 token
table in schema1.db 复制到另一个 table token
in schema2.db。我们正在使用 Android 空间来满足我们的数据库要求。
// 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。但是我得到了同样的错误。
我的设备上存在 table db1.token
,但我无法将数据从 db1.token
复制到 db2.token
。如何将数据从 db1.token
复制到 db2.token
?
更新#2
我尝试了另一种方法,其中我使用 SQLiteOpenHelper 创建了一个数据库助手并以这种方式查询 table。
/**
* 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)
参考文献:
您需要使用 'target context',即 InstrumentationRegistry.getInstrumentation().targetContext
作为打开数据库的上下文。
在错误中:
unable to open database: file:/data/user/0/com.myproject.db.test/databases/token
请注意,正在使用的应用程序包以 test
结尾,这是因为使用的上下文来自测试应用程序,而不是应用程序 'under test'。
我让它按如下方式工作
第 1 步:创建 SqLiteOpenHelper
class DataMigrationHelper @Inject constructor(
context: Context
): SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase?) {
// Already created.
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
// No need for an upgrade
}
companion object {
const val DB_NAME = "item_cache.db"
const val DB_VERSION = 19
}
}
第 2 步:将此实例传递给迁移任务
class MyMigration_From_4_To_5 @Inject constructor(
private val migrationHelper: DataMigrationHelper
): Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
val migrationDatabase: SQLiteDatabase = migrationHelper.readableDatabase
val nameValueList: MutableList<Array<Any>> = mutableListOf()
with(migrationDatabase) {
var cursor: Cursor? = null
try {
cursor = rawQuery("SELECT userId, deviceToken FROM token", null)
if (cursor.moveToFirst()) {
do {
nameValueList.add(arrayOf(cursor.getString(0), cursor.getString(1)))
} while (cursor.moveToNext())
} else {
// To get around the compilation error
// 'if' must have both main and 'else' branches if used as an expression
}
} catch (e: SQLiteException) {
// Swallow the exception.
} finally {
cursor?.close()
}
}
nameValueList.forEach {
database.execSQL(
"INSERT INTO `token`(userId, deviceToken) VALUES(?, ?)",
it
)
}
}
}