Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLiteOpenHelper.setWriteAheadLoggingEnabled causes an error log line

When I call

setWriteAheadLoggingEnabled(true);

in the constructor of my SQLiteOpenHelper subclass, the first time the database is used, after it is created, I can see the following error log item:

02-07 18:16:05.131  10426-10426/com.test E/SQLiteLog﹕ (1) no such table: test

When the application is killed and started subsequent times, I get:

E/SQLiteLog﹕ (283) recovered 10 frames from WAL file /data/data/com.test/databases/test-wal

However, the application works fine and no exception is actually thrown.

When I don't enable WAL, the log item is not there.

Is the log error something to worry about?

Edit: the code of my DbHelper:

public class DbHelper extends SQLiteOpenHelper {

private static final int DB_VERSION = 1;

public DbHelper(Context context) {
    super(context, Db.NAME, null, DB_VERSION);

    // setWriteAheadLoggingEnabled(true);
}

@Override
public void onConfigure(SQLiteDatabase db) {
    super.onConfigure(db);

    db.setForeignKeyConstraintsEnabled(true);
    db.enableWriteAheadLogging();
}

@Override
public void onCreate(SQLiteDatabase db) {
    db.execSQL(Db.TestGroup._CREATE_TABLE);
    db.execSQL(Db.Test._CREATE_TABLE);
    db.execSQL(Db.Task._CREATE_TABLE);
    db.execSQL(Db.TaskFullTextSearch._CREATE_TABLE);

    db.execSQL(Db.TaskFullTextSearch.Triggers.AFTER_INSERT);
    db.execSQL(Db.TaskFullTextSearch.Triggers.BEFORE_UPDATE);
    db.execSQL(Db.TaskFullTextSearch.Triggers.AFTER_UPDATE);
    db.execSQL(Db.TaskFullTextSearch.Triggers.BEFORE_DELETE);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // nothing
}

public int getTestCount() {
    Cursor cursor = getReadableDatabase().rawQuery(SELECT_TEST_COUNT, null);
    cursor.moveToNext();
    int count = cursor.getInt(0);
    cursor.close();

    return count;
}
}

The DB class is a 'contract' class and defines the table classes and columns and a bunch of SQL statements.

The database helper is a singleton created in a custom Application subclass:

public class TestApplication extends Application {

private static DbHelper DB_HELPER;

@Override
public void onCreate() {
    super.onCreate();

    DB_HELPER = new DbHelper(getApplicationContext());
}

public static DbHelper getDbHelper() {
    return DB_HELPER;
}
}

Edit 2: I've just checked what happens when there really is no test table - the application crashes with:

E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.test, PID: 30663
android.database.sqlite.SQLiteException: no such table: test (code 1): , while compiling: select count(*) from test
like image 1000
wujek Avatar asked Mar 29 '26 02:03

wujek


1 Answers

After some experimentation, it appears that for some reason SQLite keeps a cached list of tables as part of the metadata associated with opened connections. Creating a new table only updates the cache on the connection that was used to do it. If a connection is used to perform some operation on a table that is not included in it's cached metadata, then it complains about the table not existing, even though the operation itself is performed successfully (and the cache subsequently updated).

The connection pool used by SQLiteDatabase currently allows only one connection for both read and write operations if write-ahead logging is not enabled, presumably in order to avoid errors caused by a read operation performed while a writer has an exclusive lock on the database. If write-ahead logging is enabled, then it allows at least one separate read connection in addition to the primary write connection (the exact limit on read connections is defined by a system property).

SQLiteOpenHelper opens a read connection for getting the version information before opening the write connection that is used for database initialization and upgrades/downgrades. If write-ahead logging is not enabled, then they are both the same connection. However, if write-ahead logging is enabled before initialization, the read connection's cache does not reflect any structural changes performed during initialization (and SQLite complains about that in the log when the first query is performed). This can be fixed by enabling write-ahead logging from the onOpen() method, which is called after the initialization is completed.

As for the log about frame recovery, that's caused by the fact that SQLite checkpoints and deletes the WAL files when all the connections are closed. If the process holding the connections is terminated without explicitly closing them, then this cleanup is performed when a new connection is subsequently opened, and SQLite complains about it. If you close the SQLiteDatabase whenever your application is moved to the background, then this issue should be resolved. On the other hand, if you are interacting with the database throughout your app, then that might be problematic and difficult to implement, and I wouldn't recommend doing it.

like image 111
corsair992 Avatar answered Apr 01 '26 08:04

corsair992



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!