Summary
net.zetetic.database.sqlcipher.driver.SQLCipherStatement implements androidx.sqlite.SQLiteStatement, and its getColumnType(int) should return androidx.sqlite's SQLITE_DATA_* constants. It currently returns android.database.Cursor.getType(index) unchanged. The two agree for INTEGER, FLOAT, TEXT and BLOB (1–4), but NULL is Cursor.FIELD_TYPE_NULL = 0 versus SQLITE_DATA_NULL = 5. So every NULL column reports 0.
Affected: 4.18.0 (where the Room 3 driver was added), 4.19.0, and master @ bc71616.
Impact
Code that switches on the contract constants fails on the first NULL value. With Room 3's room3-sqlite-wrapper, roomDatabase.getSupportWrapper().query(...) throws while building the Cursor:
java.lang.IllegalStateException: Unknown column type: 0
at androidx.room3.support.RoomSupportSQLite.toCursor(RoomSupportSQLite.kt:126)
at androidx.room3.support.RoomSupportSQLiteDatabase$query$1$1.invokeSuspend(RoomSupportSQLiteDatabase.kt:154)
at androidx.room3.coroutines.PassthroughConnection.usePrepared(PassthroughConnectionPool.kt:165)
...
We hit this in a production app migrating to Room 3: its ContentProvider queries go through getSupportWrapper(), and they crashed on rows with NULL columns. Room-generated DAO code uses isNull(), so plain DAO usage isn't affected.
Minimal reproduction
System.loadLibrary("sqlcipher")
val connection = SQLCipherDriver("pw".toByteArray(), null, null).open(dbFile.absolutePath)
connection.prepare("SELECT NULL").use { statement ->
statement.step()
statement.getColumnType(0) // expected SQLITE_DATA_NULL (5), actual 0
}
Reference fix
A fix with two instrumented tests is in a personal fork. It is not submitted as a PR here, pending your contributor-agreement process: ZoeyJones#1
- It maps each
Cursor.FIELD_TYPE_* to the matching SQLite.SQLITE_DATA_* explicitly, and throws on an unknown value.
- The tests fail on unmodified
master (expected:<5> but was:<0>, and the Unknown column type: 0 above).
- With the fix, the whole
net.zetetic.database.sqlcipher.driver package passes: 37/37 on an API 36 arm64 emulator.
Thank you for maintaining SQLCipher for Android.
Summary
net.zetetic.database.sqlcipher.driver.SQLCipherStatementimplementsandroidx.sqlite.SQLiteStatement, and itsgetColumnType(int)should return androidx.sqlite'sSQLITE_DATA_*constants. It currently returnsandroid.database.Cursor.getType(index)unchanged. The two agree for INTEGER, FLOAT, TEXT and BLOB (1–4), but NULL isCursor.FIELD_TYPE_NULL = 0versusSQLITE_DATA_NULL = 5. So every NULL column reports0.Affected: 4.18.0 (where the Room 3 driver was added), 4.19.0, and
master@bc71616.Impact
Code that switches on the contract constants fails on the first NULL value. With Room 3's
room3-sqlite-wrapper,roomDatabase.getSupportWrapper().query(...)throws while building the Cursor:We hit this in a production app migrating to Room 3: its ContentProvider queries go through
getSupportWrapper(), and they crashed on rows with NULL columns. Room-generated DAO code usesisNull(), so plain DAO usage isn't affected.Minimal reproduction
Reference fix
A fix with two instrumented tests is in a personal fork. It is not submitted as a PR here, pending your contributor-agreement process: ZoeyJones#1
Cursor.FIELD_TYPE_*to the matchingSQLite.SQLITE_DATA_*explicitly, and throws on an unknown value.master(expected:<5> but was:<0>, and theUnknown column type: 0above).net.zetetic.database.sqlcipher.driverpackage passes: 37/37 on an API 36 arm64 emulator.Thank you for maintaining SQLCipher for Android.