Android SQLite возвращает устаревшие данные после изменения таблицыAndroid

Форум для тех, кто программирует под Android
Ответить
Гость
 Android SQLite возвращает устаревшие данные после изменения таблицы

Сообщение Гость »


У нас возникла странная ошибка с Android SQLite: когда мы создаем базу данных, закрываем ее, снова открываем, добавляем столбцы и выполняем запрос, например. снова количество столбцов, мы получаем старое значение.
Мы используем новейшие встроенные инструменты и целевую версию SDK (33). Вот короткий тест, демонстрирующий проблему:

Код: Выделить всё

import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase.*
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteOpenHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class SmallBugTest {

private val TEST_DB = "small-bug-test"
private val context = InstrumentationRegistry.getInstrumentation().context
private lateinit var db: SupportSQLiteDatabase

@Before
fun setup() {
//The bug doesn't seem to appear if everything is done is one session (at least with this suite), so let's simulate two sessions
/* Database is created */
SupportSQLiteOpenHelper.Configuration(context, TEST_DB,
object : SupportSQLiteOpenHelper.Callback(1) {
override fun onCreate(db: SupportSQLiteDatabase) = createDb(db)
override fun onUpgrade(
db: SupportSQLiteDatabase,
oldVersion: Int,
newVersion: Int
) = throw AssertionError()
}).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase }.close()

/* Database is closed, e.g.  приложение закрыто */

/////////////////////////////////// ////////

/* База данных открыта, но не создана */

db = SupportSQLiteOpenHelper.Configuration(context, TEST_DB,
object : SupportSQLiteOpenHelper.Callback(1) {
переопределить fun onCreate(db: SupportSQLiteDatabase) = throw AssertionError()
переопределить fun onUpgrade(
db: SupportSQLiteDatabase,
oldVersion: Int,< br /> newVersion: Int
) = throw AssertionError()
}).let { FrameworkSQLiteOpenHelperFactory().create(it).writableDatabase  
Private fun createDb(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE TABLE `TEST_TABLE` (" + //
"`column0` TEXT NOT NULL," +
" `column1` TEXT NOT NULL" +
")"
)
db.execSQL("INSERT INTO `TEST_TABLE` (`column0`, `column1`) VALUES ('content0', ' content1')")

db.version = 1
}

@Test
fun CauseBug() { //Если этот тест пройден успешно, произошла ошибка
db.query("SELECT * FROM TEST_TABLE").close()

modifySchema(db)

db.query("SELECT * FROM TEST_TABLE").use {
it.moveToFirst()
AssertArrayEquals(
arrayOf("column0", "column1"),
it.columnNames
) // Должно быть ["column0", "column1", "column2"]
 
Private fun ModifySchema(db: SupportSQLiteDatabase) {
db.execSQL( "ALTER TABLE `TEST_TABLE` RENAME TO `TEST_TABLE_OLD`")
db.execSQL(
"CREATE TABLE `TEST_TABLE` (" + //
"`column0` TEXT NOT NULL," +
"`column1` TEXT NOT NULL", +
"`column2` TEXT" +
")"
)
db.execSQL("INSERT INTO `TEST_TABLE ` (`column0`, `column1`) SELECT `column0`, `column1` FROM `TEST_TABLE_OLD`")
db.execSQL("DROP TABLE `TEST_TABLE_OLD`")
db.update(
"TEST_TABLE",
CONFLICT_NONE,
ContentValues().also { it.put("column2", "content2"); },
null,
null
)
}
}
Это то, что мы уже выяснили: запрос отправляется на

Код: Выделить всё

(Support)SQLiteDatabase
a matching

Код: Выделить всё

PreparedStatement
is returned by

Код: Выделить всё

acquirePreparedStatement
and then executed.

Код: Выделить всё

acquirePreparedStatement
either creates a new PS and then caches it in the

Код: Выделить всё

PreparedStatementCache mPreparedStatementCache
or returns a previously cached PS. A PS matches if it was created by exactly the same sql text (adding backticks or a comment will cause a new PS to be returned accordingly.)

Some operations on the cursor for a query (e.g.

Код: Выделить всё

Cursor#getX
) will always be populated with fresh data for every call to , but some operations like

Код: Выделить всё

getColumnCount
or

Код: Выделить всё

getColumnNames
will only be populated once per PS (or more precisely: They always seem to be populated with the data from the time of creation of the PS.)
Accordingly if a query is dispatched and cached and then the table is changed to have more columns and then a query with the same sql text is dispatched, the resulting cursor from the second query will use the same PS internally and return correct content values for the new columns, but will still report the same (now outdated!)

Код: Выделить всё

columnCounts
and

Код: Выделить всё

columnNames
.
There's also an issue in the Google bug tracker where we've noted our findings but haven't found a workaround yet: https://issuetracker.google.com/issues/ ... #comment18
Have you encountered this problem and found a workaround?
We created this sample project if you want to see it in action: https://github.com/SailReal/Android-Issue-153521693


Источник: https://stackoverflow.com/questions/780 ... ring-table
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»