From 7f3219c4890161f22e8ea970838a0da8ac01a62d Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Fri, 10 Jul 2026 08:45:49 -0500 Subject: [PATCH 01/17] Add support for beginTransactionWithListenerReadOnly --- .../sqlcipher_cts/SQLCipherDatabaseTest.java | 12 ++++++++++++ .../zetetic/database/sqlcipher/SQLiteDatabase.java | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java index 0e59dfb..811ac79 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java @@ -1,6 +1,7 @@ package net.zetetic.database.sqlcipher_cts; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.fail; @@ -10,6 +11,8 @@ import android.database.sqlite.SQLiteException; import android.util.Log; +import androidx.sqlite.db.SupportSQLiteDatabase; + import net.zetetic.database.sqlcipher.SQLiteCursor; import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteDatabaseConfiguration; @@ -19,6 +22,7 @@ import java.io.File; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -30,6 +34,14 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase { + @Test + public void shouldDeclareBeginTransactionWithListenerReadOnlyOverride() throws NoSuchMethodException { + Method method = SQLiteDatabase.class.getDeclaredMethod( + "beginTransactionWithListenerReadOnly", + android.database.sqlite.SQLiteTransactionListener.class); + assertThat(method, is(notNullValue())); + } + @Test public void testCreateDatabaseConnectionWithStringPassword() { try { diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java index 2fba349..4552792 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java @@ -405,6 +405,11 @@ public void beginTransactionReadOnly() { beginTransaction(); } + public void beginTransactionWithListenerReadOnly( + android.database.sqlite.SQLiteTransactionListener transactionListener){ + beginTransactionWithListener(transactionListener); + } + /** * Begins a transaction in EXCLUSIVE mode. *

From 7f88ad954a0c42a486db50d04d2e3a05e6950e74 Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Mon, 27 Jul 2026 09:35:36 -0500 Subject: [PATCH 02/17] Update test for SQLCipher 4.17.0 --- .../zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java index a5ea345..7933403 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java @@ -17,6 +17,6 @@ public void shouldExtractLibraryCipherVersion() { cipherVersion = cursor.getString(0); cursor.close(); } - assertThat(cipherVersion, containsString("4.14.0")); + assertThat(cipherVersion, containsString("4.17.0")); } } From 4a180284bb56d1682b06cd52cc74737966d39cda Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Mon, 27 Jul 2026 09:35:58 -0500 Subject: [PATCH 03/17] Update formatting --- .../database/sqlcipher_cts/SQLiteQueryBuilderTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java index 99a7ac1..22f7705 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java @@ -55,10 +55,12 @@ public class SQLiteQueryBuilderTest { @Before public void setUp() throws Exception { System.loadLibrary("sqlcipher"); - File f = ApplicationProvider.getApplicationContext().getDatabasePath(DATABASE_FILE); - f.mkdirs(); - if (f.exists()) { f.delete(); } - mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null); + File databasePath = ApplicationProvider.getApplicationContext().getDatabasePath(DATABASE_FILE); + databasePath.mkdirs(); + if (databasePath.exists()) { + databasePath.delete(); + } + mDatabase = SQLiteDatabase.openOrCreateDatabase(databasePath,null); assertNotNull(mDatabase); } From e18dec7b655687350b97341272095372d6aa33aa Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Mon, 27 Jul 2026 10:05:59 -0500 Subject: [PATCH 04/17] Use the test orchestrator to help isolated JUnit tests https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/runner#use-android --- sqlcipher/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle index 872bcb6..80b649b 100644 --- a/sqlcipher/build.gradle +++ b/sqlcipher/build.gradle @@ -26,6 +26,9 @@ android { testInstrumentationRunnerArguments clearPackageData: 'true' consumerProguardFiles 'consumer-rules.pro' } + testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' + } buildTypes { release { @@ -164,6 +167,7 @@ dependencies { androidTestImplementation 'androidx.test:core:1.7.0' androidTestImplementation 'org.hamcrest:hamcrest-library:3.0' testImplementation 'junit:junit:4.13.2' + androidTestUtil 'androidx.test:orchestrator:1.6.1' } allprojects { From 41f5cbf7e9f09d54ad186179f43549d8b9f9b41c Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Mon, 27 Jul 2026 10:41:40 -0500 Subject: [PATCH 05/17] Wait for the Room invalidation tracker to finish before changing password This test would occasionally fail because the invalidation tracker runs on a background thread and might trigger after the insert but during the reconfigure method run after we change the password to rebuild the connection pool. The primary connection will be closed temporarily and this can impact the invalidation tracker when it tries to get a connection from the connection pool. We just wait for the invalidation tracker to complete before proceeding with our change password operation. https://developer.android.com/reference/androidx/room/InvalidationTracker#addObserver(androidx.room.InvalidationTracker.Observer) --- .../sqlcipher_cts/SupportAPIRoomTest.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java index 1162ed9..7711468 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java @@ -7,12 +7,14 @@ import android.content.Context; import android.database.Cursor; +import androidx.annotation.NonNull; import androidx.room.ColumnInfo; import androidx.room.Dao; import androidx.room.Database; import androidx.room.Delete; import androidx.room.Entity; import androidx.room.Insert; +import androidx.room.InvalidationTracker; import androidx.room.OnConflictStrategy; import androidx.room.PrimaryKey; import androidx.room.Query; @@ -34,7 +36,10 @@ import java.io.File; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class SupportAPIRoomTest { @@ -100,8 +105,16 @@ public void shouldQueryDataByParametersViaDao(){ } @Test - public void shouldSupportChangingPasswordWithRoom(){ + public void shouldSupportChangingPasswordWithRoom() throws InterruptedException { + var latch = new CountDownLatch(1); + db.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("User") { + @Override + public void onInvalidated(@NonNull Set tables) { + latch.countDown(); + } + }); userDao.insert(new User("foo", "bar")); + latch.await(5, TimeUnit.SECONDS); SQLiteDatabase database = (SQLiteDatabase)db.getOpenHelper().getWritableDatabase(); database.changePassword(UUID.randomUUID().toString()); List users = userDao.getAll(); From 8d908fde5a4eace464086dec2e87cc0d49729cf1 Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Mon, 27 Jul 2026 12:39:57 -0500 Subject: [PATCH 06/17] Adjustments to TestAbstractCursor to get ignored tests to pass - Report success in onMove to allow ignored tests to pass. The call to cursorFillWindow would call moveToPosition, but would never report a successful move which prevented the tests from passing. - Also requiring adding some guards to the getString implementation for invalid values. --- .../database_cts/AbstractCursorTest.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java index 2db7b1e..fce60a7 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.annotation.SuppressLint; import android.content.Context; import android.database.CharArrayBuffer; import android.database.ContentObserver; @@ -42,7 +41,6 @@ import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -193,8 +191,6 @@ public void testOnChange() throws InterruptedException { assertTrue(mock.hadCalledOnChange()); } - @SuppressLint("IgnoreWithoutReason") - @Ignore @Test public void testOnMove() { assertFalse(mTestAbstractCursor.getOnMoveRet()); @@ -209,8 +205,6 @@ public void testOnMove() { assertEquals(5, mTestAbstractCursor.getNewPos()); } - @SuppressLint("IgnoreWithoutReason") - @Ignore @Test public void testOnMove_samePosition() { mTestAbstractCursor.moveToFirst(); @@ -367,8 +361,6 @@ public void testDeactivate() { assertTrue(mock.hadCalledOnInvalid()); } - @SuppressLint("IgnoreWithoutReason") - @Ignore @Test public void testCopyStringToBuffer() { CharArrayBuffer ca = new CharArrayBuffer(1000); @@ -384,8 +376,6 @@ public void testCopyStringToBuffer() { assertEquals(sb.toString(), new String(ca.data, 0, ca.sizeCopied)); } - @SuppressLint("IgnoreWithoutReason") - @Ignore @Test public void testCheckPosition() { // Test with position = -1. @@ -527,6 +517,7 @@ public boolean onMove(int oldPosition, int newPosition) { mOldPosition = oldPosition; mNewPosition = newPosition; mRowsMovedSum += Math.abs(newPosition - oldPosition); + mOnMoveReturnValue = true; return mOnMoveReturnValue; } @@ -542,7 +533,14 @@ public String[] getColumnNames() { @Override public String getString(int columnIndex) { - Object cell = mRows[mPos].get(columnIndex); + if(mPos < 0){ + return null; + } + var row = mRows[mPos]; + if(row == null) { + return null; + } + Object cell = row.get(columnIndex); return (cell == null) ? null : cell.toString(); } @@ -583,7 +581,7 @@ public boolean isNull(int column) { @Override public int getType(int column) { - return 0; + return FIELD_TYPE_STRING; } public boolean hadCalledOnChange() { From 17ee46c3c39239ab578ce4dac0d5e0460a7b292a Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Mon, 27 Jul 2026 13:05:58 -0500 Subject: [PATCH 07/17] Prevent null pointer exception in copyStringToBuffer --- .../database/database_cts/CursorWindowTest.java | 17 +++++++++++++++++ .../java/net/zetetic/database/CursorWindow.java | 12 ++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java index 02df1b4..abbc1d6 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java @@ -106,6 +106,23 @@ public void testNull() { assertNull(window.getBlob(0, 0)); } + @Test + public void testShouldNotThrowNullPointerExceptionWhenNullValueReturnedFromGetString() { + var window = getOneByOneWindow(); + assertTrue(window.putNull(0, 0)); + var buffer = new CharArrayBuffer(16); + window.copyStringToBuffer(0, 0, buffer); + } + + @Test + public void testShouldSetSizeCopiedToZeroWhenNullValueReturnedFromGetString() { + var window = getOneByOneWindow(); + assertTrue(window.putNull(0, 0)); + var buffer = new CharArrayBuffer(16); + window.copyStringToBuffer(0, 0, buffer); + assertEquals(0, buffer.sizeCopied); + } + @Test public void testEmptyString() { CursorWindow window = getOneByOneWindow(); diff --git a/sqlcipher/src/main/java/net/zetetic/database/CursorWindow.java b/sqlcipher/src/main/java/net/zetetic/database/CursorWindow.java index a604305..8ed83d8 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/CursorWindow.java +++ b/sqlcipher/src/main/java/net/zetetic/database/CursorWindow.java @@ -320,10 +320,14 @@ public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) { if (buffer == null) { throw new IllegalArgumentException("CharArrayBuffer should not be null"); } - // TODO not as optimal as the original code - char[] chars = getString(row, column).toCharArray(); - buffer.data = chars; - buffer.sizeCopied = chars.length; + var content = getString(row, column); + if(content != null){ + var chars = content.toCharArray(); + buffer.data = chars; + buffer.sizeCopied = chars.length; + } else { + buffer.sizeCopied = 0; + } } /** From 7cf28a83d28c243bd144467d757f7c7644abcf5a Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Wed, 29 Jul 2026 08:39:47 -0500 Subject: [PATCH 08/17] Update AGP to 9.3.0 and Gradle to 9.5.0 --- build.gradle | 2 +- gradle.properties | 1 - gradle/wrapper/gradle-wrapper.properties | 6 +++--- sqlcipher/build.gradle | 11 ++++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 6975f17..14d1b11 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - def gradleToolsVersion = "8.9.0" + def gradleToolsVersion = "9.3.0" classpath "com.android.tools.build:gradle:${gradleToolsVersion}" } } diff --git a/gradle.properties b/gradle.properties index 223c6a0..e3164b5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,5 +17,4 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -android.enableJetifier=true android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff702a8..b8144b8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -#Thu Feb 19 08:45:35 CST 2026 +#Wed Jul 29 08:02:53 CDT 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionSha256Sum=553c78f50dafcd54d65b9a444649057857469edf836431389695608536d6b746 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle index 80b649b..23ae093 100644 --- a/sqlcipher/build.gradle +++ b/sqlcipher/build.gradle @@ -11,14 +11,14 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING def log = services.get(StyledTextOutputFactory).create("sqlcipher") android { - compileSdkVersion 34 + compileSdkVersion 37 namespace "net.zetetic.database" defaultConfig { minSdkVersion "${rootProject.ext.minSdkVersion}" - targetSdkVersion 34 + targetSdkVersion 37 versionCode 1 versionName "${rootProject.ext.libraryVersion}" - project.archivesBaseName = "sqlcipher-android-${versionName}" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' // The following argument makes the Android Test Orchestrator run its // "pm clear" command after each test invocation. This command ensures @@ -26,6 +26,9 @@ android { testInstrumentationRunnerArguments clearPackageData: 'true' consumerProguardFiles 'consumer-rules.pro' } + base { + archivesName = "sqlcipher-android-${rootProject.ext.libraryVersion}" + } testOptions { execution 'ANDROIDX_TEST_ORCHESTRATOR' } @@ -33,11 +36,9 @@ android { buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } releaseNoX86 { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64' } From dba933b32064abf231c148cf5b57feb5c340aa71 Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Wed, 29 Jul 2026 11:34:01 -0500 Subject: [PATCH 09/17] Implement SupportSQLiteQuery on SQLiteQuery, add tests --- build.gradle | 2 +- sqlcipher/build.gradle | 3 - .../DatabaseGeneralTest.java | 3 +- .../database/sqlcipher_cts/SQLiteFtsTest.java | 1 - .../sqlcipher_cts/SQLiteQueryTest.java | 283 +++++++++++++++++- .../database/sqlcipher/SQLiteQuery.java | 57 +++- sqlciphertest/build.gradle | 3 - 7 files changed, 337 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 14d1b11..b7127a2 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ project.ext { libraryVersion = "4.17.0" } minSdkVersion = 23 - androidXSQLiteVersion = "2.6.2" + androidXSQLiteVersion = "2.7.0" roomVersion = "2.8.4" androidNdkVersion = "25.2.9519653" mavenLocalRepositoryPrefix = "file://" diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle index 23ae093..9fd1a9c 100644 --- a/sqlcipher/build.gradle +++ b/sqlcipher/build.gradle @@ -63,9 +63,6 @@ android { ndkVersion "${rootProject.ext.androidNdkVersion}" - useLibrary 'android.test.base' // for android.test.AndroidTestCase - useLibrary 'android.test.runner' // for android.test.MoreAsserts - java { toolchain { languageVersion = JavaLanguageVersion.of(17) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java index 636aefa..f25041b 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java @@ -27,7 +27,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; import android.database.CharArrayBuffer; @@ -47,9 +46,9 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteStatement; import net.zetetic.database.DefaultDatabaseErrorHandler; -import junit.framework.Assert; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java index 4708ddd..da0a344 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.res.Resources; import android.database.Cursor; -import android.test.AndroidTestCase; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryTest.java index befb784..77bf1e9 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryTest.java @@ -16,10 +16,285 @@ package net.zetetic.database.sqlcipher_cts; -import junit.framework.TestCase; -public class SQLiteQueryTest extends TestCase { - public void testMethods() { - // cannot obtain an instance of SQLiteQuery +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import android.os.CancellationSignal; + +import androidx.sqlite.db.SupportSQLiteProgram; + +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteQuery; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class SQLiteQueryTest extends AndroidSQLCipherTestCase { + + private RecordingProgram program; + + @Before + public void setup(){ + program = new RecordingProgram(); + } + + private SQLiteQuery newQuery(String sql) { + try { + var ctor = SQLiteQuery.class.getDeclaredConstructor( + SQLiteDatabase.class, + String.class, + CancellationSignal.class); + ctor.setAccessible(true); + return ctor.newInstance(database, sql, new CancellationSignal()); + } catch (Exception ex){ + throw new RuntimeException(ex); + } + } + + @Test + public void shouldReturnExactStatementWithGetSql() { + var sql = "SELECT ?, ?"; + try (SQLiteQuery query = newQuery(sql)) { + assertEquals(sql, query.getSql()); + } + } + + @Test + public void shouldMatchPlaceholderCountFromGetArgCount() { + try (SQLiteQuery query = newQuery("SELECT ?, ?, ?")) { + assertEquals(3, query.getArgCount()); + } + } + + @Test + public void shouldReturnZeroFromGetArgCountWhenNoPlaceholders() { + try (SQLiteQuery query = newQuery("SELECT 1")) { + assertEquals(0, query.getArgCount()); + } + } + + @Test + public void shouldBindLongValue() { + try (SQLiteQuery query = newQuery("SELECT ?")) { + query.bindLong(1, 42L); + query.bindTo(program); + assertEquals(1, program.calls.size()); + program.calls.get(0).assertLong(1, 42L); + } + } + + @Test + public void shouldBindDoubleValue() { + try (SQLiteQuery query = newQuery("SELECT ?")) { + query.bindDouble(1, 3.14159); + query.bindTo(program); + assertEquals(1, program.calls.size()); + program.calls.get(0).assertDouble(1, 3.14159); + } + } + + @Test + public void shouldBindStringValue() { + try (SQLiteQuery query = newQuery("SELECT ?")) { + query.bindString(1, "hello world"); + query.bindTo(program); + assertEquals(1, program.calls.size()); + program.calls.get(0).assertString(1, "hello world"); + } + } + + @Test + public void shouldBindBlobValue() { + var blob = new byte[] { 1, 2, 3, 4, 5 }; + try (SQLiteQuery query = newQuery("SELECT ?")) { + query.bindBlob(1, blob); + query.bindTo(program); + assertEquals(1, program.calls.size()); + program.calls.get(0).assertBlob(1, blob); + } + } + + @Test + public void shouldBindsNullWhenExplicitlyBound() { + try (SQLiteQuery query = newQuery("SELECT ?")) { + query.bindNull(1); + query.bindTo(program); + assertEquals(1, program.calls.size()); + program.calls.get(0).assertNull(1); + } + } + + @Test + public void shouldBindNullWhenArgLeftUnbound() { + try (SQLiteQuery query = newQuery("SELECT ?")) { + query.bindTo(program); + assertEquals(1, program.calls.size()); + program.calls.get(0).assertNull(1); + } + } + + @Test + public void shouldBindsMultipleArgsInOrderWithMixedTypes() { + var blob = new byte[] { 9, 8, 7 }; + try (SQLiteQuery query = newQuery("SELECT ?, ?, ?, ?, ?")) { + query.bindLong(1, 100L); + query.bindString(2, "middle"); + query.bindDouble(3, 2.5); + query.bindNull(4); + query.bindBlob(5, blob); + query.bindTo(program); + assertEquals(5, program.calls.size()); + program.calls.get(0).assertLong(1, 100L); + program.calls.get(1).assertString(2, "middle"); + program.calls.get(2).assertDouble(3, 2.5); + program.calls.get(3).assertNull(4); + program.calls.get(4).assertBlob(5, blob); + } + } + + @Test + public void shouldPerformNoOpBindWhenStatementHasNoParameters() { + try (SQLiteQuery query = newQuery("SELECT 1")) { + query.bindTo(program); + assertEquals(0, program.calls.size()); + } + } + + @Test + public void shouldBindAllArgsThenBindToTranslatesEachSupportedType() { + var blob = new byte[] { 0x0A, 0x0B }; + try (SQLiteQuery query = newQuery("SELECT ?, ?, ?, ?, ?, ?")) { + query.bindAllArgs(true, (byte) 7, (short) 8, 9, blob, "text"); + query.bindTo(program); + assertEquals(6, program.calls.size()); + program.calls.get(0).assertLong(1, 1L); + program.calls.get(1).assertLong(2, 7L); + program.calls.get(2).assertLong(3, 8L); + program.calls.get(3).assertLong(4, 9L); + program.calls.get(4).assertBlob(5, blob); + program.calls.get(5).assertString(6, "text"); + } + } + + private static final class RecordingProgram implements SupportSQLiteProgram { + final List calls = new ArrayList<>(); + + @Override + public void bindNull(int index) { + calls.add(Call.ofNull(index)); + } + + @Override + public void bindLong(int index, long value) { + calls.add(Call.ofLong(index, value)); + } + + @Override + public void bindDouble(int index, double value) { + calls.add(Call.ofDouble(index, value)); + } + + @Override + public void bindString(int index, String value) { + calls.add(Call.ofString(index, value)); + } + + @Override + public void bindBlob(int index, byte[] value) { + calls.add(Call.ofBlob(index, value)); + } + + @Override + public void clearBindings() { + calls.clear(); + } + + @Override + public void close() {} + } + + private static final class Call { + enum Kind { + NULL, + LONG, + DOUBLE, + STRING, + BLOB + } + + final Kind kind; + final int index; + final long longValue; + final double doubleValue; + final String stringValue; + final byte[] blobValue; + + private Call( + Kind kind, + int index, + long longValue, + double doubleValue, + String stringValue, + byte[] blobValue) { + this.kind = kind; + this.index = index; + this.longValue = longValue; + this.doubleValue = doubleValue; + this.stringValue = stringValue; + this.blobValue = blobValue; + } + + static Call ofNull(int index) { + return new Call(Kind.NULL, index, 0, 0, null, null); + } + + static Call ofLong(int index, long value) { + return new Call(Kind.LONG, index, value, 0, null, null); + } + + static Call ofDouble(int index, double value) { + return new Call(Kind.DOUBLE, index, 0, value, null, null); + } + + static Call ofString(int index, String value) { + return new Call(Kind.STRING, index, 0, 0, value, null); + } + + static Call ofBlob(int index, byte[] value) { + return new Call(Kind.BLOB, index, 0, 0, null, value); + } + + void assertNull(int expectedIndex) { + assertEquals(Kind.NULL, kind); + assertEquals(expectedIndex, index); + } + + void assertLong(int expectedIndex, long expectedValue) { + assertEquals(Kind.LONG, kind); + assertEquals(expectedIndex, index); + assertEquals(expectedValue, longValue); + } + + void assertDouble(int expectedIndex, double expectedValue) { + assertEquals(Kind.DOUBLE, kind); + assertEquals(expectedIndex, index); + assertEquals(expectedValue, doubleValue, 0.0); + } + + void assertString(int expectedIndex, String expectedValue) { + assertEquals(Kind.STRING, kind); + assertEquals(expectedIndex, index); + assertEquals(expectedValue, stringValue); + } + + void assertBlob(int expectedIndex, byte[] expectedValue) { + assertEquals(Kind.BLOB, kind); + assertEquals(expectedIndex, index); + assertArrayEquals(expectedValue, blobValue); + } } } diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java index aae818b..c475847 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java @@ -25,6 +25,10 @@ import android.os.CancellationSignal; import android.os.OperationCanceledException; +import androidx.annotation.NonNull; +import androidx.sqlite.db.SupportSQLiteProgram; +import androidx.sqlite.db.SupportSQLiteQuery; + import net.zetetic.database.CursorWindow; import net.zetetic.database.Logger; @@ -35,7 +39,7 @@ * This class is not thread-safe. *

*/ -public final class SQLiteQuery extends SQLiteProgram { +public final class SQLiteQuery extends SQLiteProgram implements SupportSQLiteQuery { private static final String TAG = "SQLiteQuery"; private final CancellationSignal mCancellationSignal; @@ -88,4 +92,55 @@ window, startPos, requiredPos, countAllRows, getConnectionFlags(), public String toString() { return "SQLiteQuery: " + getSql(); } + + @NonNull + @Override + public String getSql() { + return super.getSql(); + } + + @Override + public void bindTo(@NonNull SupportSQLiteProgram supportSQLiteProgram) { + Object[] bindArgs = super.getBindArgs(); + if (bindArgs == null) { + return; + } + for (var i = 0; i < bindArgs.length; i++) { + var index = i + 1; + Object arg = bindArgs[i]; + bindArgumentToProgram(supportSQLiteProgram, index, arg); + } + } + + @Override + public int getArgCount() { + var bindArgs = super.getBindArgs(); + return bindArgs == null + ? 0 + : bindArgs.length; + } + + private void bindArgumentToProgram(@NonNull SupportSQLiteProgram program, int index, Object arg) { + if (arg == null) { + program.bindNull(index); + } else if (arg instanceof byte[]) { + program.bindBlob(index, (byte[]) arg); + } else if (arg instanceof Float || arg instanceof Double) { + program.bindDouble(index, ((Number) arg).doubleValue()); + } else if (arg instanceof Boolean) { + var value = ((Boolean) arg) + ? 1L + : 0L; + program.bindLong(index, value); + } else if (arg instanceof Integer || arg instanceof Long + || arg instanceof Short || arg instanceof Byte) { + program.bindLong(index, ((Number) arg).longValue()); + } else if (arg instanceof String) { + program.bindString(index, (String) arg); + } else { + var message = "Cannot bind " + arg + " at index " + index + + " supported types: null, byte[], float, double, int, long, boolean, String"; + throw new IllegalArgumentException(message); + } + } } diff --git a/sqlciphertest/build.gradle b/sqlciphertest/build.gradle index b1e5bbf..13ef12e 100644 --- a/sqlciphertest/build.gradle +++ b/sqlciphertest/build.gradle @@ -18,9 +18,6 @@ android { } ndkVersion "21.3.6528147" - - useLibrary 'android.test.base' // for android.test.AndroidTestCase - useLibrary 'android.test.runner' // for android.test.ApplicationTestCase extends AndroidTestCase } dependencies { From 212477e20c21389f06ad865a41543aedf0fb63ec Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Wed, 29 Jul 2026 14:25:53 -0500 Subject: [PATCH 10/17] Initial implementation for SQLiteDriver, SQLiteConnection, SQLiteStatement for Room3 --- .../sqlcipher/driver/SQLCipherConnection.java | 27 ++++ .../sqlcipher/driver/SQLCipherDriver.java | 34 +++++ .../sqlcipher/driver/SQLCipherStatement.java | 136 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java create mode 100644 sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java create mode 100644 sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java new file mode 100644 index 0000000..10e73a9 --- /dev/null +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java @@ -0,0 +1,27 @@ +package net.zetetic.database.sqlcipher.driver; + +import androidx.annotation.NonNull; +import androidx.sqlite.SQLiteConnection; +import androidx.sqlite.SQLiteStatement; + +import net.zetetic.database.sqlcipher.SQLiteDatabase; + +public class SQLCipherConnection implements SQLiteConnection { + private final SQLiteDatabase database; + + public SQLCipherConnection( + SQLiteDatabase database) { + this.database = database; + } + + @NonNull + @Override + public SQLiteStatement prepare(@NonNull String sql) { + return new SQLCipherStatement(database, sql); + } + + @Override + public void close() { + database.close(); + } +} diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java new file mode 100644 index 0000000..bf5b661 --- /dev/null +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java @@ -0,0 +1,34 @@ +package net.zetetic.database.sqlcipher.driver; + +import androidx.annotation.NonNull; +import androidx.sqlite.SQLiteConnection; +import androidx.sqlite.SQLiteDriver; + +import net.zetetic.database.DatabaseErrorHandler; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; + +import java.io.File; + +public class SQLCipherDriver implements SQLiteDriver { + + private final byte[] passphrase; + private final SQLiteDatabaseHook hook; + private final DatabaseErrorHandler handler; + + public SQLCipherDriver( + byte[] passphrase, + SQLiteDatabaseHook hook, + DatabaseErrorHandler handler){ + this.passphrase = passphrase; + this.hook = hook; + this.handler = handler; + } + + @NonNull + @Override + public SQLiteConnection open(@NonNull String filename) { + var db = SQLiteDatabase.openOrCreateDatabase(filename, passphrase, null, handler, hook); + return new SQLCipherConnection(db); + } +} diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java new file mode 100644 index 0000000..eb33838 --- /dev/null +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java @@ -0,0 +1,136 @@ +package net.zetetic.database.sqlcipher.driver; + +import android.database.Cursor; + +import androidx.annotation.NonNull; +import androidx.sqlite.SQLiteStatement; + +import net.zetetic.database.sqlcipher.SQLiteDatabase; + +import java.util.LinkedHashMap; +import java.util.Map; + +@SuppressWarnings("resource") +public class SQLCipherStatement implements SQLiteStatement { + + private final SQLiteDatabase database; + private final String sql; + private final Map bindings = new LinkedHashMap<>(); + private Cursor cursor; + + public SQLCipherStatement( + SQLiteDatabase database, + String sql) { + this.database = database; + this.sql = sql; + } + + @Override + public void bindBlob(int index, @NonNull byte[] value) { + bindings.put(index, value); + } + + @Override + public void bindDouble(int index, double value) { + bindings.put(index, value); + } + + @Override + public void bindLong(int index, long value) { + bindings.put(index, value); + } + + @Override + public void bindText(int index, @NonNull String value) { + bindings.put(index, value); + } + + @Override + public void bindNull(int index) { + bindings.put(index, null); + } + + @NonNull + @Override + public byte[] getBlob(int index) { + return requireCursor().getBlob(index); + } + + @Override + public double getDouble(int index) { + return requireCursor().getDouble(index); + } + + @Override + public long getLong(int index) { + return requireCursor().getLong(index); + } + + @NonNull + @Override + public String getText(int index) { + return requireCursor().getString(index); + } + + @Override + public boolean isNull(int index) { + return requireCursor().isNull(index); + } + + @Override + public int getColumnCount() { + return requireCursor().getColumnCount(); + } + + @NonNull + @Override + public String getColumnName(int index) { + return requireCursor().getColumnName(index); + } + + @Override + public int getColumnType(int index) { + return requireCursor().getType(index); + } + + @Override + public boolean step() { + if (cursor == null) { + var maxIndex = 0; + for (int key : bindings.keySet()) { + maxIndex = Math.max(maxIndex, key); + } + var args = new Object[maxIndex]; + for (int i = 1; i <= maxIndex; i++) { + args[i - 1] = bindings.get(i); + } + cursor = database.rawQuery(sql, args); + } + return cursor.moveToNext(); + } + + @Override + public void reset() { + if (cursor != null) { + cursor.close(); + cursor = null; + } + } + + @Override + public void clearBindings() { + bindings.clear(); + } + + @Override + public void close() { + + } + + private Cursor requireCursor() { + if (cursor == null) { + throw new IllegalStateException("step() must be called before reading column values"); + } + return cursor; + } +} From ae0d1a79fe42d50d60fe7a7c59d89829f85f19aa Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Wed, 29 Jul 2026 14:34:57 -0500 Subject: [PATCH 11/17] Adjust test packages to match source packages --- .../database/{database_cts => }/AbstractCursorTest.java | 5 ++--- .../database/{database_cts => }/CursorJoinerTest.java | 2 +- .../database/{database_cts => }/CursorWindowTest.java | 5 +---- .../database/{database_cts => }/CursorWrapperTest.java | 2 +- .../database/{database_cts => }/DatabaseCursorTest.java | 4 ++-- .../DatabaseUtilsInsertHelperTest.java | 2 +- .../database/{database_cts => }/DatabaseUtilsTest.java | 3 +-- .../database/{database_cts => }/MergeCursorTest.java | 2 +- .../java/net/zetetic/database/{database_cts => }/README | 0 .../AndroidSQLCipherTestCase.java | 6 +----- .../CipherCompatibilityTest.java | 7 +------ .../DatabaseGeneralTest.java | 4 +--- .../DatabaseStatementTest.java | 5 +---- .../ImportUnencryptedDatabaseTest.java | 4 +--- .../{sqlcipher_cts => sqlcipher}/JsonCastTest.java | 2 +- .../MigrateDatabaseFrom1xFormatToCurrentFormatTest.java | 6 +----- .../zetetic/database/{sqlcipher_cts => sqlcipher}/README | 0 .../{sqlcipher_cts => sqlcipher}/RoomUpsertTest.java | 4 +--- .../SQLCipherDatabaseTest.java | 9 +-------- .../SQLCipherOpenHelperTest.java | 8 +------- .../SQLCipherVersionTest.java | 2 +- .../{ => sqlcipher}/SQLCipherWALTestScenario.java | 6 +----- .../SQLiteAbortExceptionTest.java | 2 +- .../{sqlcipher_cts => sqlcipher}/SQLiteClosableTest.java | 4 +--- .../SQLiteConstraintExceptionTest.java | 2 +- .../{sqlcipher_cts => sqlcipher}/SQLiteCursorTest.java | 5 +---- .../SQLiteDatabaseCorruptExceptionTest.java | 2 +- .../{sqlcipher_cts => sqlcipher}/SQLiteDatabaseTest.java | 8 +------- .../SQLiteDiskIOExceptionTest.java | 2 +- .../SQLiteDoneExceptionTest.java | 2 +- .../SQLiteExceptionTest.java | 2 +- .../{sqlcipher_cts => sqlcipher}/SQLiteFtsTest.java | 4 +--- .../SQLiteFullExceptionTest.java | 2 +- .../SQLiteMisuseExceptionTest.java | 2 +- .../SQLiteOpenHelperTest.java | 7 +------ .../{sqlcipher_cts => sqlcipher}/SQLiteProgramTest.java | 5 +---- .../SQLiteQueryBuilderTest.java | 8 +------- .../{sqlcipher_cts => sqlcipher}/SQLiteQueryTest.java | 5 +---- .../SQLiteStatementTest.java | 4 +--- .../{sqlcipher_cts => sqlcipher}/SupportAPIRoomTest.java | 5 +---- .../{sqlcipher_cts => sqlcipher}/SupportHelperTest.java | 4 +--- 41 files changed, 41 insertions(+), 122 deletions(-) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/AbstractCursorTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/CursorJoinerTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/CursorWindowTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/CursorWrapperTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/DatabaseCursorTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/DatabaseUtilsInsertHelperTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/DatabaseUtilsTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/MergeCursorTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{database_cts => }/README (100%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/AndroidSQLCipherTestCase.java (98%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/CipherCompatibilityTest.java (82%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_android => sqlcipher}/DatabaseGeneralTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/DatabaseStatementTest.java (98%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/ImportUnencryptedDatabaseTest.java (94%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/JsonCastTest.java (93%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/MigrateDatabaseFrom1xFormatToCurrentFormatTest.java (88%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/README (100%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/RoomUpsertTest.java (94%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLCipherDatabaseTest.java (98%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLCipherOpenHelperTest.java (92%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLCipherVersionTest.java (92%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{ => sqlcipher}/SQLCipherWALTestScenario.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteAbortExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteClosableTest.java (96%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteConstraintExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteCursorTest.java (98%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteDatabaseCorruptExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteDatabaseTest.java (99%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteDiskIOExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteDoneExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteFtsTest.java (97%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteFullExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteMisuseExceptionTest.java (95%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteOpenHelperTest.java (94%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteProgramTest.java (97%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteQueryBuilderTest.java (98%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteQueryTest.java (98%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SQLiteStatementTest.java (98%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SupportAPIRoomTest.java (96%) rename sqlcipher/src/androidTest/java/net/zetetic/database/{sqlcipher_cts => sqlcipher}/SupportHelperTest.java (97%) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/AbstractCursorTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/AbstractCursorTest.java index fce60a7..d90e44f 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/AbstractCursorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -28,8 +28,7 @@ import android.database.ContentObserver; import android.database.CursorIndexOutOfBoundsException; import android.database.DataSetObserver; -import net.zetetic.database.AbstractCursor; -import net.zetetic.database.CursorWindow; + import net.zetetic.database.sqlcipher.SQLiteDatabase; import android.net.Uri; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorJoinerTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/CursorJoinerTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorJoinerTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/CursorJoinerTest.java index 494a386..866ff87 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorJoinerTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/CursorJoinerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertEquals; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/CursorWindowTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/CursorWindowTest.java index abbc1d6..2a13ee1 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/CursorWindowTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -28,9 +28,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.CursorWindow; -import net.zetetic.database.MatrixCursor; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWrapperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/CursorWrapperTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWrapperTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/CursorWrapperTest.java index 4372c2f..3e89e5d 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWrapperTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/CursorWrapperTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertEquals; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseCursorTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseCursorTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseCursorTest.java index 4d161d1..9f17260 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseCursorTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseCursorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -29,7 +29,7 @@ import android.database.CursorIndexOutOfBoundsException; import android.database.CursorWrapper; import android.database.DataSetObserver; -import net.zetetic.database.DatabaseUtils; + import net.zetetic.database.sqlcipher.SQLiteCursor; import net.zetetic.database.sqlcipher.SQLiteCursorDriver; import net.zetetic.database.sqlcipher.SQLiteDatabase; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsInsertHelperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseUtilsInsertHelperTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsInsertHelperTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseUtilsInsertHelperTest.java index 1d97d19..238ff8d 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsInsertHelperTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseUtilsInsertHelperTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertArrayEquals; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseUtilsTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseUtilsTest.java index a130cf1..aca138d 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/DatabaseUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertArrayEquals; @@ -36,7 +36,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.DatabaseUtils; import net.zetetic.database.DatabaseUtils.InsertHelper; import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteStatement; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/MergeCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/MergeCursorTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/MergeCursorTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/MergeCursorTest.java index 4671c7d..8c45eeb 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/MergeCursorTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/MergeCursorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.database_cts; +package net.zetetic.database; import static org.junit.Assert.assertEquals; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/README b/sqlcipher/src/androidTest/java/net/zetetic/database/README similarity index 100% rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/README rename to sqlcipher/src/androidTest/java/net/zetetic/database/README diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/AndroidSQLCipherTestCase.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/AndroidSQLCipherTestCase.java similarity index 98% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/AndroidSQLCipherTestCase.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/AndroidSQLCipherTestCase.java index 07bc2ef..270657e 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/AndroidSQLCipherTestCase.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/AndroidSQLCipherTestCase.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.content.Context; import android.icu.text.NumberFormat; @@ -7,16 +7,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import net.zetetic.database.sqlcipher.SQLiteDatabase; - import org.junit.After; import org.junit.Before; -import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/CipherCompatibilityTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/CipherCompatibilityTest.java similarity index 82% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/CipherCompatibilityTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/CipherCompatibilityTest.java index 7f0c2d6..9346571 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/CipherCompatibilityTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/CipherCompatibilityTest.java @@ -1,18 +1,13 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.MatcherAssert.assertThat; import android.database.Cursor; -import net.zetetic.database.sqlcipher.SQLiteConnection; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; - import org.junit.Test; import java.io.File; -import java.io.IOException; public class CipherCompatibilityTest extends AndroidSQLCipherTestCase { diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/DatabaseGeneralTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/DatabaseGeneralTest.java index f25041b..0541c0c 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/DatabaseGeneralTest.java @@ -15,7 +15,7 @@ */ // modified from original source see README at the top level of this project -package net.zetetic.database.sqlcipher_android; +package net.zetetic.database.sqlcipher; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -43,8 +43,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteStatement; import net.zetetic.database.DefaultDatabaseErrorHandler; import org.junit.After; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/DatabaseStatementTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/DatabaseStatementTest.java similarity index 98% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/DatabaseStatementTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/DatabaseStatementTest.java index 29e465b..8da26f4 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/DatabaseStatementTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/DatabaseStatementTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -28,9 +28,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteStatement; - import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/ImportUnencryptedDatabaseTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/ImportUnencryptedDatabaseTest.java similarity index 94% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/ImportUnencryptedDatabaseTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/ImportUnencryptedDatabaseTest.java index 6d56676..9a1f981 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/ImportUnencryptedDatabaseTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/ImportUnencryptedDatabaseTest.java @@ -1,12 +1,10 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.Matchers.is; import static org.hamcrest.MatcherAssert.assertThat; import android.database.Cursor; -import net.zetetic.database.sqlcipher.SQLiteDatabase; - import org.junit.Test; import java.io.File; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/JsonCastTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/JsonCastTest.java similarity index 93% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/JsonCastTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/JsonCastTest.java index d9fc707..9b2b6ce 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/JsonCastTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/JsonCastTest.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/MigrateDatabaseFrom1xFormatToCurrentFormatTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/MigrateDatabaseFrom1xFormatToCurrentFormatTest.java similarity index 88% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/MigrateDatabaseFrom1xFormatToCurrentFormatTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/MigrateDatabaseFrom1xFormatToCurrentFormatTest.java index a203de5..f66ac96 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/MigrateDatabaseFrom1xFormatToCurrentFormatTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/MigrateDatabaseFrom1xFormatToCurrentFormatTest.java @@ -1,14 +1,10 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.Matchers.is; import static org.hamcrest.MatcherAssert.assertThat; import android.database.Cursor; -import net.zetetic.database.sqlcipher.SQLiteConnection; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; - import org.junit.Test; import java.io.File; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/README b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/README similarity index 100% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/README rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/README diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/RoomUpsertTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/RoomUpsertTest.java similarity index 94% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/RoomUpsertTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/RoomUpsertTest.java index a2d6fca..1e70bde 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/RoomUpsertTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/RoomUpsertTest.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -16,8 +16,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SupportOpenHelperFactory; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherDatabaseTest.java similarity index 98% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherDatabaseTest.java index 811ac79..c8564b4 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherDatabaseTest.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; @@ -11,13 +11,6 @@ import android.database.sqlite.SQLiteException; import android.util.Log; -import androidx.sqlite.db.SupportSQLiteDatabase; - -import net.zetetic.database.sqlcipher.SQLiteCursor; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteDatabaseConfiguration; -import net.zetetic.database.sqlcipher.SQLiteStatement; - import org.junit.Test; import java.io.File; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherOpenHelperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherOpenHelperTest.java similarity index 92% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherOpenHelperTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherOpenHelperTest.java index 6cd997d..1e26adb 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherOpenHelperTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherOpenHelperTest.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.core.Is.is; @@ -7,12 +7,6 @@ import android.content.Context; import android.util.Log; -import net.zetetic.database.sqlcipher.SQLiteConnection; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; -import net.zetetic.database.sqlcipher.SQLiteOpenHelper; -import net.zetetic.database.sqlcipher.SQLiteStatement; - import org.junit.Test; import java.io.File; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherVersionTest.java similarity index 92% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherVersionTest.java index 7933403..36192b7 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherVersionTest.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringContains.containsString; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/SQLCipherWALTestScenario.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherWALTestScenario.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/SQLCipherWALTestScenario.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherWALTestScenario.java index 1540587..d76c198 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/SQLCipherWALTestScenario.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherWALTestScenario.java @@ -1,4 +1,4 @@ -package net.zetetic.database; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertEquals; @@ -14,10 +14,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import net.zetetic.database.sqlcipher.SQLiteConnection; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteOpenHelper; - import java.io.File; import java.io.FileInputStream; import java.util.Arrays; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteAbortExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteAbortExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteAbortExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteAbortExceptionTest.java index 51c1873..f7b7e7e 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteAbortExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteAbortExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteAbortException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteClosableTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteClosableTest.java similarity index 96% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteClosableTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteClosableTest.java index e1d5e14..e1a61c0 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteClosableTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteClosableTest.java @@ -14,14 +14,12 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import net.zetetic.database.sqlcipher.SQLiteClosable; - import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteConstraintExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteConstraintExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteConstraintExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteConstraintExceptionTest.java index 261f865..1d9f97e 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteConstraintExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteConstraintExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteConstraintException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteCursorTest.java similarity index 98% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteCursorTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteCursorTest.java index 1fc54cf..3cfe7c7 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteCursorTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteCursorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertEquals; @@ -33,9 +33,6 @@ import net.zetetic.database.AbstractCursor; import net.zetetic.database.CursorWindow; -import net.zetetic.database.sqlcipher.SQLiteCursor; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteDirectCursorDriver; import org.junit.After; import org.junit.Before; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseCorruptExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDatabaseCorruptExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseCorruptExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDatabaseCorruptExceptionTest.java index 378e08f..09a1928 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseCorruptExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDatabaseCorruptExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteDatabaseCorruptException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDatabaseTest.java similarity index 99% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDatabaseTest.java index cccd81f..4b4d75e 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDatabaseTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -31,13 +31,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import net.zetetic.database.DatabaseUtils; -import net.zetetic.database.sqlcipher.SQLiteCursor; -import net.zetetic.database.sqlcipher.SQLiteCursorDriver; -import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteDatabase.CursorFactory; -import net.zetetic.database.sqlcipher.SQLiteQuery; -import net.zetetic.database.sqlcipher.SQLiteStatement; -import net.zetetic.database.sqlcipher.SQLiteTransactionListener; import org.junit.After; import org.junit.Before; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDiskIOExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDiskIOExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDiskIOExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDiskIOExceptionTest.java index febd2e3..7d074f2 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDiskIOExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDiskIOExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteDiskIOException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDoneExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDoneExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDoneExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDoneExceptionTest.java index 384903a..64c4880 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDoneExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteDoneExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteDoneException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteExceptionTest.java index 92d2c56..6cb88ee 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteFtsTest.java similarity index 97% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteFtsTest.java index da0a344..7fbff11 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteFtsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -27,8 +27,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SQLiteDatabase; - import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFullExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteFullExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFullExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteFullExceptionTest.java index 2e62200..1f62840 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFullExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteFullExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteFullException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteMisuseExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteMisuseExceptionTest.java similarity index 95% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteMisuseExceptionTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteMisuseExceptionTest.java index 3b91cd5..17e466f 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteMisuseExceptionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteMisuseExceptionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.sqlite.SQLiteMisuseException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteOpenHelperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteOpenHelperTest.java similarity index 94% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteOpenHelperTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteOpenHelperTest.java index 5adc5c7..cee20c1 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteOpenHelperTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteOpenHelperTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -29,12 +29,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SQLiteCursor; -import net.zetetic.database.sqlcipher.SQLiteCursorDriver; -import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteDatabase.CursorFactory; -import net.zetetic.database.sqlcipher.SQLiteOpenHelper; -import net.zetetic.database.sqlcipher.SQLiteQuery; import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteProgramTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteProgramTest.java similarity index 97% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteProgramTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteProgramTest.java index 5317b03..058e9b0 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteProgramTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteProgramTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertArrayEquals; @@ -31,9 +31,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteStatement; - import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteQueryBuilderTest.java similarity index 98% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteQueryBuilderTest.java index 22f7705..2258aa5 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteQueryBuilderTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import android.database.Cursor; @@ -24,12 +24,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SQLiteCursor; -import net.zetetic.database.sqlcipher.SQLiteCursorDriver; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteQuery; -import net.zetetic.database.sqlcipher.SQLiteQueryBuilder; - import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteQueryTest.java similarity index 98% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteQueryTest.java index 77bf1e9..1da5321 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteQueryTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertArrayEquals; @@ -24,9 +24,6 @@ import androidx.sqlite.db.SupportSQLiteProgram; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteQuery; - import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteStatementTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteStatementTest.java similarity index 98% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteStatementTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteStatementTest.java index 1ae8d8f..bd9e7de 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteStatementTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLiteStatementTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertArrayEquals; @@ -28,8 +28,6 @@ import android.content.Context; import android.database.Cursor; import net.zetetic.database.DatabaseUtils; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SQLiteStatement; import android.database.SQLException; import android.database.sqlite.SQLiteDoneException; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportAPIRoomTest.java similarity index 96% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportAPIRoomTest.java index 7711468..9e1149d 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportAPIRoomTest.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.is; @@ -25,9 +25,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.SQLiteDatabase; -import net.zetetic.database.sqlcipher.SupportOpenHelperFactory; - import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportHelperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportHelperTest.java similarity index 97% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportHelperTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportHelperTest.java index fc6caf2..e8b99e0 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportHelperTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportHelperTest.java @@ -1,4 +1,4 @@ -package net.zetetic.database.sqlcipher_cts; +package net.zetetic.database.sqlcipher; import static org.junit.Assert.assertEquals; @@ -11,8 +11,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import net.zetetic.database.sqlcipher.SupportHelper; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; From 1098fbdb61e62572a2c752de8f19a1a41b8805eb Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Fri, 31 Jul 2026 10:04:28 -0500 Subject: [PATCH 12/17] Adjustments to get Room3 tests to pass --- build.gradle | 1 + sqlcipher/build.gradle | 12 +- .../sqlcipher/SupportAPIRoomTest.java | 172 ------------------ .../driver/Room3SQLCipherDriverTest.java | 96 ++++++++++ .../{ => driver}/RoomUpsertTest.java | 47 +++-- .../sqlcipher/driver/SupportAPIRoomTest.kt | 151 +++++++++++++++ .../sqlcipher/driver/SQLCipherConnection.java | 9 + .../sqlcipher/driver/SQLCipherDriver.java | 5 +- .../sqlcipher/driver/SQLCipherStatement.java | 69 +++++-- 9 files changed, 346 insertions(+), 216 deletions(-) delete mode 100644 sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportAPIRoomTest.java create mode 100644 sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java rename sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/{ => driver}/RoomUpsertTest.java (57%) create mode 100644 sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt diff --git a/build.gradle b/build.gradle index b7127a2..c5d454a 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,7 @@ plugins { id('maven-publish') id('signing') id("io.github.gradle-nexus.publish-plugin") version "1.1.0" + id('com.google.devtools.ksp') version '2.3.10' } allprojects { diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle index 9fd1a9c..79620f9 100644 --- a/sqlcipher/build.gradle +++ b/sqlcipher/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.library' apply plugin: "maven-publish" apply plugin: "signing" +apply plugin: "com.google.devtools.ksp" import org.gradle.internal.logging.text.StyledTextOutputFactory import static org.gradle.internal.logging.text.StyledTextOutput.Style @@ -154,10 +155,13 @@ dependencies { // Needed for the Support API interfaces implementation "androidx.sqlite:sqlite:${rootProject.ext.androidXSQLiteVersion}" - // Needed for the Support API in tests - androidTestImplementation "androidx.room:room-common:${rootProject.ext.roomVersion}" - androidTestImplementation "androidx.room:room-runtime:${rootProject.ext.roomVersion}" - androidTestAnnotationProcessor "androidx.room:room-compiler:${rootProject.ext.roomVersion}" + // Needed for the Room library in tests + def room_version = "3.0.1" + androidTestImplementation "androidx.room3:room3-runtime:$room_version" + kspAndroidTest "androidx.room3:room3-compiler:$room_version" + + // Needed for runTest + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0" // Needed for supporting tests androidTestImplementation 'androidx.test.ext:junit:1.3.0' diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportAPIRoomTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportAPIRoomTest.java deleted file mode 100644 index 9e1149d..0000000 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SupportAPIRoomTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package net.zetetic.database.sqlcipher; - -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import android.content.Context; -import android.database.Cursor; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Dao; -import androidx.room.Database; -import androidx.room.Delete; -import androidx.room.Entity; -import androidx.room.Insert; -import androidx.room.InvalidationTracker; -import androidx.room.OnConflictStrategy; -import androidx.room.PrimaryKey; -import androidx.room.Query; -import androidx.room.Room; -import androidx.room.RoomDatabase; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.SupportSQLiteOpenHelper; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@RunWith(AndroidJUnit4.class) -public class SupportAPIRoomTest { - - private AppDatabase db; - private UserDao userDao; - private File databaseFile; - - @Before - public void before(){ - Context context = ApplicationProvider.getApplicationContext(); - System.loadLibrary("sqlcipher"); - databaseFile = context.getDatabasePath("users.db"); - if(databaseFile.exists()){ - databaseFile.delete(); - } - SupportOpenHelperFactory factory = new SupportOpenHelperFactory("user".getBytes(StandardCharsets.UTF_8)); - db = Room.databaseBuilder(context, AppDatabase.class, databaseFile.getAbsolutePath()) - .openHelperFactory(factory).build(); - db.clearAllTables(); - userDao = db.userDao(); - } - - @Test - public void shouldInsertDataViaDao(){ - User user = new User("John", "Doe"); - user.uid = userDao.insert(user); - assertThat(user.uid, is(not(0))); - } - - @Test - public void shouldDeleteDataViaDao(){ - User user = new User("foo", "bar"); - user.uid = 1; - SupportSQLiteOpenHelper helper = db.getOpenHelper(); - SupportSQLiteDatabase database = helper.getWritableDatabase(); - database.execSQL("insert into user values(?,?,?);", - new Object[]{user.uid, user.firstName, user.lastName}); - Cursor cursor = database.query("select count(*) from user;"); - cursor.moveToFirst(); - boolean userInserted = cursor.getInt(0) > 0; - assertThat(userInserted, is(true)); - userDao.delete(user); - cursor = database.query("select count(*) from user;"); - cursor.moveToFirst(); - int existingUsers = cursor.getInt(0); - cursor.close(); - assertThat(existingUsers, is(0)); - } - - @Test - public void shouldQueryDataByParametersViaDao(){ - User user = new User("foo", "bar"); - user.uid = 1; - SupportSQLiteOpenHelper helper = db.getOpenHelper(); - SupportSQLiteDatabase database = helper.getWritableDatabase(); - database.execSQL("insert into user values(?,?,?);", - new Object[]{user.uid, user.firstName, user.lastName}); - User foundUser = userDao.findByName(user.firstName, user.lastName); - assertThat(foundUser.uid, is(user.uid)); - assertThat(foundUser.firstName, is(user.firstName)); - assertThat(foundUser.lastName, is(user.lastName)); - } - - @Test - public void shouldSupportChangingPasswordWithRoom() throws InterruptedException { - var latch = new CountDownLatch(1); - db.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("User") { - @Override - public void onInvalidated(@NonNull Set tables) { - latch.countDown(); - } - }); - userDao.insert(new User("foo", "bar")); - latch.await(5, TimeUnit.SECONDS); - SQLiteDatabase database = (SQLiteDatabase)db.getOpenHelper().getWritableDatabase(); - database.changePassword(UUID.randomUUID().toString()); - List users = userDao.getAll(); - assertThat(users.size(), is(1)); - } - - @After - public void after(){ - if(db != null){ - db.close(); - if(databaseFile != null){ - databaseFile.delete(); - } - } - } - - @Database(entities = {User.class}, version = 1, exportSchema = false) - public abstract static class AppDatabase extends RoomDatabase { - public abstract UserDao userDao(); - } - - @Entity - public static class User { - @PrimaryKey(autoGenerate = true) - public long uid; - @ColumnInfo(name = "first_name") - public String firstName; - @ColumnInfo(name = "last_name") - public String lastName; - - public User(String firstName, String lastName) { - this.firstName = firstName; - this.lastName = lastName; - } - } - - @Dao - public interface UserDao { - @Query("SELECT * FROM user") - List getAll(); - - @Query("SELECT * FROM user WHERE uid IN (:userIds)") - List loadAllByIds(int[] userIds); - - @Query("SELECT * FROM user WHERE first_name LIKE :first AND " + - "last_name LIKE :last LIMIT 1") - User findByName(String first, String last); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - long insert(User user); - - @Delete - void delete(User user); - - @Query("DELETE FROM user;") - void deleteAll(); - } -} diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java new file mode 100644 index 0000000..3fa452a --- /dev/null +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java @@ -0,0 +1,96 @@ +package net.zetetic.database.sqlcipher.driver; + +import android.content.Context; + +import androidx.room3.Dao; +import androidx.room3.Database; +import androidx.room3.Entity; +import androidx.room3.Insert; +import androidx.room3.PrimaryKey; +import androidx.room3.Query; +import androidx.room3.Room; +import androidx.room3.RoomDatabase; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import net.zetetic.database.sqlcipher.driver.SQLCipherDriver; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class Room3SQLCipherDriverTest { + + private Context context; + private SQLCipherDriver driver; + + @Before + public void setup(){ + context = ApplicationProvider.getApplicationContext(); + System.loadLibrary("sqlcipher"); + driver = new SQLCipherDriver( + "password".getBytes(), + null, + null); + } + + @Test + public void shouldTestSQLCipherDriver(){ + var database = Room.databaseBuilder( + context, + UserDatabase.class, + "users.db") + .setDriver(driver) + .build(); + var dao = database.userDao(); + var name = "Nick Parker"; + dao.insert(new User(1, name)); + var result = dao.get(1); + + Assert.assertNotNull(result); + Assert.assertEquals(1, result.id); + Assert.assertEquals(name, result.name); + database.close(); + } + + @Test + public void shouldRetrieveCipherVersion(){ + var connection = driver.open(":memory:"); + var stmt = connection.prepare("PRAGMA cipher_version;"); + stmt.step(); + var version = stmt.getText(0); + Assert.assertEquals("4.17.0 community", version); + connection.close(); + } + + @Entity + public static class User { + @PrimaryKey + public long id; + public String name; + + public User(long id, String name) { + this.id = id; + this.name = name; + } + } + + @Dao + public interface UserDao { + @Insert + void insert(User user); + + @Query("SELECT * FROM User WHERE id = :id") + User get(long id); + } + + @Database( + entities = {User.class}, + version = 1 + ) + public abstract static class UserDatabase extends RoomDatabase { + public abstract UserDao userDao(); + } +} diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/RoomUpsertTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/RoomUpsertTest.java similarity index 57% rename from sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/RoomUpsertTest.java rename to sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/RoomUpsertTest.java index 1e70bde..2c71453 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/RoomUpsertTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/RoomUpsertTest.java @@ -1,57 +1,64 @@ -package net.zetetic.database.sqlcipher; +package net.zetetic.database.sqlcipher.driver; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import android.content.Context; - -import androidx.room.Dao; -import androidx.room.Database; -import androidx.room.Entity; -import androidx.room.PrimaryKey; -import androidx.room.Query; -import androidx.room.Room; -import androidx.room.RoomDatabase; -import androidx.room.Upsert; +import androidx.room3.Dao; +import androidx.room3.Database; +import androidx.room3.Entity; +import androidx.room3.PrimaryKey; +import androidx.room3.Query; +import androidx.room3.Room; +import androidx.room3.RoomDatabase; +import androidx.room3.Upsert; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import net.zetetic.database.sqlcipher.driver.SQLCipherDriver; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.File; import java.nio.charset.StandardCharsets; @RunWith(AndroidJUnit4.class) public class RoomUpsertTest { + private SQLCipherDriver driver; private UserDatabase database; private UserDao userDao; @Before public void before(){ - Context context = ApplicationProvider.getApplicationContext(); - File databaseFile = context.getDatabasePath("upsert.db"); + var context = ApplicationProvider.getApplicationContext(); + var databaseFile = context.getDatabasePath("upsert.db"); if(databaseFile.exists()){ databaseFile.delete(); } System.loadLibrary("sqlcipher"); - final byte[] passphrase = "user".getBytes(StandardCharsets.UTF_8); - SupportOpenHelperFactory factory = new SupportOpenHelperFactory(passphrase); - database = Room.databaseBuilder(context, UserDatabase.class, databaseFile.getName()) - .openHelperFactory(factory).build(); + var passphrase = "user".getBytes(StandardCharsets.UTF_8); + driver = new SQLCipherDriver( + passphrase, + null, + null); + database = Room.databaseBuilder( + context, + UserDatabase.class, + "users.db") + .setDriver(driver) + .build(); userDao = database.userDao(); } @Test public void shouldAllowUpsertBehavior(){ - User user = new User(); + var user = new User(); user.name = "Foo Bar"; user.age = 41; user.id = userDao.upsert(user); user.age = 42; userDao.upsert(user); - User[] searchUser = userDao.findById(user.id); + var searchUser = userDao.findById(user.id); assertThat(searchUser[0].age , is(42)); } diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt new file mode 100644 index 0000000..061e794 --- /dev/null +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt @@ -0,0 +1,151 @@ +package net.zetetic.database.sqlcipher.driver + +import android.content.Context +import androidx.room3.ColumnInfo +import androidx.room3.Dao +import androidx.room3.Database +import androidx.room3.Delete +import androidx.room3.Entity +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.PrimaryKey +import androidx.room3.Query +import androidx.room3.Room +import androidx.room3.RoomDatabase +import androidx.sqlite.SQLiteDriver +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.test.runTest +import org.hamcrest.CoreMatchers.not +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.empty +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.notNullValue +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File +import java.nio.charset.StandardCharsets + +@RunWith(AndroidJUnit4::class) +class SupportAPIRoomTest { + private lateinit var driver: SQLiteDriver + private lateinit var db: AppDatabase + private lateinit var userDao: UserDao + private lateinit var databaseFile: File + + @Before + fun setup(){ + val password = "user".toByteArray(StandardCharsets.UTF_8) + setup(password, true) + } + + fun setup(password: ByteArray, deleteDatabase: Boolean) { + val context = ApplicationProvider.getApplicationContext() + System.loadLibrary("sqlcipher") + databaseFile = context.getDatabasePath("users.db") + if (deleteDatabase and databaseFile.exists()) { + databaseFile.delete() + } + driver = SQLCipherDriver( + password, + null, + null + ) + db = Room.databaseBuilder( + context, + AppDatabase::class.java, + databaseFile.absolutePath) + .setDriver(driver) + .build() + userDao = db.userDao() + } + + @Test + fun shouldInsertDataViaDao() { + val user = User("John", "Doe") + user.uid = userDao.insert(user) + assertThat(user.uid, not(0L)) + } + + @Test + @Throws(InterruptedException::class) + fun shouldDeleteDataViaDao() = runTest { + val user = User("foo", "bar").apply { uid = 1 } + userDao.insert(user) + assertThat(userDao.findById(user.uid), notNullValue()) + userDao.delete(user) + assertThat(userDao.all, `is`(empty())) + } + + @Test + fun shouldQueryDataByParametersViaDao(){ + val user = User("foo", "bar").apply { uid = 1 } + userDao.insert(user) + val foundUser = userDao.findByName(user.firstName, user.lastName) + assertThat(foundUser, notNullValue()) + assertThat(foundUser!!.uid, `is`(user.uid)) + assertThat(foundUser.firstName, `is`(user.firstName)) + assertThat(foundUser.lastName, `is`(user.lastName)) + } + + @Test + fun shouldSupportChangingPasswordWithRoom() = runTest { + val newPassword = "foobar".toByteArray() + userDao.insert( User("foo", "bar")) + db.close() + val connection = driver.open(databaseFile.absolutePath) as SQLCipherConnection + connection.changePassword(newPassword) + connection.close() + setup(newPassword, false) + assertThat(userDao.all.count(), `is`(1)) + } + + @After + fun after() { + db.close() + databaseFile.delete() + } + + @Database(entities = [User::class], version = 1, exportSchema = false) + abstract class AppDatabase : RoomDatabase() { + abstract fun userDao(): UserDao + } + + @Entity + class User( + @field:ColumnInfo(name = "first_name") var firstName: String, + @field:ColumnInfo(name = "last_name") var lastName: String + ) { + @PrimaryKey(autoGenerate = true) + var uid: Long = 0 + } + + @Dao + interface UserDao { + @get:Query("SELECT * FROM user") + val all: MutableList + + @Query("SELECT * FROM user WHERE uid IN (:userIds)") + fun loadAllByIds(userIds: IntArray?): MutableList + + @Query( + "SELECT * FROM user WHERE first_name LIKE :first AND " + + "last_name LIKE :last LIMIT 1" + ) + fun findByName(first: String?, last: String?): User? + + @Query("SELECT * FROM user WHERE uid = :userId") + fun findById(userId: Long): User? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(user: User): Long + + @Delete + fun delete(user: User) + + @Query("DELETE FROM user;") + fun deleteAll() + } +} diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java index 10e73a9..2e39e54 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java @@ -20,6 +20,15 @@ public SQLiteStatement prepare(@NonNull String sql) { return new SQLCipherStatement(database, sql); } + @Override + public boolean inTransaction() { + return database.inTransaction(); + } + + public void changePassword(byte[] newPassword){ + database.changePassword(newPassword); + } + @Override public void close() { database.close(); diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java index bf5b661..5f29295 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java @@ -8,8 +8,6 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; -import java.io.File; - public class SQLCipherDriver implements SQLiteDriver { private final byte[] passphrase; @@ -27,7 +25,8 @@ public SQLCipherDriver( @NonNull @Override - public SQLiteConnection open(@NonNull String filename) { + public SQLiteConnection open( + @NonNull String filename) { var db = SQLiteDatabase.openOrCreateDatabase(filename, passphrase, null, handler, hook); return new SQLCipherConnection(db); } diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java index eb33838..c181c98 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java @@ -17,6 +17,7 @@ public class SQLCipherStatement implements SQLiteStatement { private final String sql; private final Map bindings = new LinkedHashMap<>(); private Cursor cursor; + private boolean stepped; public SQLCipherStatement( SQLiteDatabase database, @@ -26,70 +27,92 @@ public SQLCipherStatement( } @Override - public void bindBlob(int index, @NonNull byte[] value) { + public void bindBlob( + int index, + @NonNull byte[] value) { bindings.put(index, value); } @Override - public void bindDouble(int index, double value) { + public void bindDouble( + int index, + double value) { bindings.put(index, value); } @Override - public void bindLong(int index, long value) { + public void bindLong( + int index, + long value) { bindings.put(index, value); } @Override - public void bindText(int index, @NonNull String value) { + public void bindText( + int index, + @NonNull String value) { bindings.put(index, value); } @Override - public void bindNull(int index) { + public void bindNull( + int index) { bindings.put(index, null); } @NonNull @Override - public byte[] getBlob(int index) { + public byte[] getBlob( + int index) { return requireCursor().getBlob(index); } @Override - public double getDouble(int index) { + public double getDouble( + int index) { return requireCursor().getDouble(index); } @Override - public long getLong(int index) { + public long getLong( + int index) { return requireCursor().getLong(index); } @NonNull @Override - public String getText(int index) { + public String getText( + int index) { return requireCursor().getString(index); } @Override - public boolean isNull(int index) { + public boolean isNull( + int index) { return requireCursor().isNull(index); } @Override public int getColumnCount() { - return requireCursor().getColumnCount(); + if (cursor == null) { + createCursor(); + } + return cursor.getColumnCount(); } @NonNull @Override - public String getColumnName(int index) { - return requireCursor().getColumnName(index); + public String getColumnName( + int index) { + if (cursor == null) { + createCursor(); + } + return cursor.getColumnName(index); } @Override - public int getColumnType(int index) { + public int getColumnType( + int index) { return requireCursor().getType(index); } @@ -106,7 +129,8 @@ public boolean step() { } cursor = database.rawQuery(sql, args); } - return cursor.moveToNext(); + stepped = cursor.moveToNext(); + return stepped; } @Override @@ -115,6 +139,7 @@ public void reset() { cursor.close(); cursor = null; } + stepped = false; } @Override @@ -123,12 +148,22 @@ public void clearBindings() { } @Override - public void close() { + public void close() {} + private void createCursor() { + var maxIndex = 0; + for (int key : bindings.keySet()) { + maxIndex = Math.max(maxIndex, key); + } + var args = new Object[maxIndex]; + for (int i = 1; i <= maxIndex; i++) { + args[i - 1] = bindings.get(i); + } + cursor = database.rawQuery(sql, args); } private Cursor requireCursor() { - if (cursor == null) { + if (!stepped || cursor == null) { throw new IllegalStateException("step() must be called before reading column values"); } return cursor; From 078a05b8d68ae1c31b102d5718d0e86227ab897c Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Mon, 3 Aug 2026 14:27:19 -0500 Subject: [PATCH 13/17] sqlcipher-android internally maintains a connection pool --- .../zetetic/database/sqlcipher/driver/SQLCipherDriver.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java index 5f29295..edf9e34 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherDriver.java @@ -30,4 +30,9 @@ public SQLiteConnection open( var db = SQLiteDatabase.openOrCreateDatabase(filename, passphrase, null, handler, hook); return new SQLCipherConnection(db); } + + @Override + public boolean hasConnectionPool() { + return true; + } } From 748e37e09361a428e04ecd21008dd702af014e79 Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Wed, 5 Aug 2026 13:39:38 -0500 Subject: [PATCH 14/17] Add instrumentation tests for SQLCipherDriver for Room3 - Verify Room 3 integration with SQLCipherDriver, including entity insert and query round-trip. - Additional testing with both insertOrReplace and Upset behavior. - Verify PRAGMA cipher_version reports expected SQLCipher version. - Cover statementPrefix() extraction: plain statements, leading whitespace, line/block comments, comment-only and empty input. - Cover isJournalModeSet() dispatch: intercepts journal_mode assignments (case-insensitive, schema-qualified), ignores journal_mode queries to handle connection pool reconfiguration. --- .../driver/Room3SQLCipherDriverTest.java | 2 - .../sqlcipher/driver/RoomUpsertTest.java | 85 ------ ...SQLCipherConnectionStatementPrefixTests.kt | 73 +++++ .../driver/SQLCipherConnectionTests.kt | 261 ++++++++++++++++++ .../sqlcipher/driver/SupportAPIRoomTest.kt | 95 +++++-- .../driver/JournalModeStatement.java | 116 ++++++++ .../sqlcipher/driver/SQLCipherConnection.java | 82 +++++- .../sqlcipher/driver/SQLCipherStatement.java | 9 +- 8 files changed, 615 insertions(+), 108 deletions(-) delete mode 100644 sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/RoomUpsertTest.java create mode 100644 sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionStatementPrefixTests.kt create mode 100644 sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionTests.kt create mode 100644 sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/JournalModeStatement.java diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java index 3fa452a..e7593f7 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java @@ -13,8 +13,6 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import net.zetetic.database.sqlcipher.driver.SQLCipherDriver; - import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/RoomUpsertTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/RoomUpsertTest.java deleted file mode 100644 index 2c71453..0000000 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/RoomUpsertTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package net.zetetic.database.sqlcipher.driver; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import androidx.room3.Dao; -import androidx.room3.Database; -import androidx.room3.Entity; -import androidx.room3.PrimaryKey; -import androidx.room3.Query; -import androidx.room3.Room; -import androidx.room3.RoomDatabase; -import androidx.room3.Upsert; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import net.zetetic.database.sqlcipher.driver.SQLCipherDriver; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.nio.charset.StandardCharsets; - -@RunWith(AndroidJUnit4.class) -public class RoomUpsertTest { - private SQLCipherDriver driver; - private UserDatabase database; - private UserDao userDao; - - @Before - public void before(){ - var context = ApplicationProvider.getApplicationContext(); - var databaseFile = context.getDatabasePath("upsert.db"); - if(databaseFile.exists()){ - databaseFile.delete(); - } - System.loadLibrary("sqlcipher"); - var passphrase = "user".getBytes(StandardCharsets.UTF_8); - driver = new SQLCipherDriver( - passphrase, - null, - null); - database = Room.databaseBuilder( - context, - UserDatabase.class, - "users.db") - .setDriver(driver) - .build(); - userDao = database.userDao(); - } - - @Test - public void shouldAllowUpsertBehavior(){ - var user = new User(); - user.name = "Foo Bar"; - user.age = 41; - user.id = userDao.upsert(user); - user.age = 42; - userDao.upsert(user); - var searchUser = userDao.findById(user.id); - assertThat(searchUser[0].age , is(42)); - } - - @Entity - public static class User { - @PrimaryKey(autoGenerate = true) long id; - String name; - int age; - } - - @Dao - public static abstract class UserDao { - @Upsert - abstract long upsert(User user); - @Query("SELECT * FROM user WHERE id=:id") - abstract User[] findById(long id); - } - - @Database(entities = {User.class}, version = 1, exportSchema = false) - public static abstract class UserDatabase extends RoomDatabase { - abstract UserDao userDao(); - } - -} diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionStatementPrefixTests.kt b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionStatementPrefixTests.kt new file mode 100644 index 0000000..1e8a81b --- /dev/null +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionStatementPrefixTests.kt @@ -0,0 +1,73 @@ +package net.zetetic.database.sqlcipher.driver + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertNull +import junit.framework.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SQLCipherConnectionStatementPrefixTests { + + @Test + fun shouldExtractPrefixFromPlainStatements() { + assertEquals("SEL", SQLCipherConnection.statementPrefix("SELECT * FROM t")) + assertEquals("BEG", SQLCipherConnection.statementPrefix("BEGIN")) + assertEquals("PRA", SQLCipherConnection.statementPrefix("PRAGMA user_version")) + } + + @Test + fun shouldSkipLeadingWhitespace() { + assertEquals("BEG", SQLCipherConnection.statementPrefix(" \n\t BEGIN IMMEDIATE")) + } + + @Test + fun shouldSkipLineComments() { + assertEquals("BEG", SQLCipherConnection.statementPrefix("-- start txn\nBEGIN IMMEDIATE")) + } + + @Test + fun shouldSkipBlockComments() { + assertEquals("COM", SQLCipherConnection.statementPrefix("/* finish\n the txn */ COMMIT")) + } + + @Test + fun shouldReturnNullForCommentOnlyInput() { + assertNull(SQLCipherConnection.statementPrefix("-- nothing here")) + assertNull(SQLCipherConnection.statementPrefix("/* nothing here */")) + } + + @Test + fun shouldTolerateShortInput() { + assertNull(SQLCipherConnection.statementPrefix("")) + assertNull(SQLCipherConnection.statementPrefix(" ")) + } + + @Test + fun shouldInterceptJournalModeSets() { + assertTrue(journalFor("PRAGMA journal_mode = WAL")) + assertTrue(journalFor("pragma journal_mode=delete")) + assertTrue(journalFor("PRAGMA main.journal_mode = TRUNCATE")) + } + + @Test + fun shouldNotInterceptJournalModeQueries() { + assertFalse(journalFor("PRAGMA journal_mode")) + } + + @Test + fun shouldNotInterceptOtherPragmas() { + assertFalse(journalFor("PRAGMA foreign_keys = ON")) + assertFalse(journalFor("PRAGMA busy_timeout = 3000")) + assertFalse(journalFor("PRAGMA synchronous = NORMAL")) + } + + private fun journalFor(sql: String): Boolean { + val trimmed = sql.trim() + return SQLCipherConnection.isJournalModeSet( + SQLCipherConnection.statementPrefix(trimmed), + trimmed) + } +} \ No newline at end of file diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionTests.kt b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionTests.kt new file mode 100644 index 0000000..5eee460 --- /dev/null +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnectionTests.kt @@ -0,0 +1,261 @@ +package net.zetetic.database.sqlcipher.driver + +import android.content.Context +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.execSQL +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.test.runTest +import net.zetetic.database.sqlcipher.SQLiteDatabase +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File + +/** + * Verifying that SQLCipherConnection.prepare() dispatches transaction SQL + * and the journal_mode pragma to the SQLiteSession-aware paths, and + * everything else to the standard SQLCipherStatement. + */ +@RunWith(AndroidJUnit4::class) +class SQLCipherConnectionTests { + + private lateinit var dbFile: File + private lateinit var database: SQLiteDatabase + private lateinit var connection: SQLCipherConnection + + @Before + fun setUp() { + System.loadLibrary("sqlcipher") + val context: Context = InstrumentationRegistry.getInstrumentation().targetContext + dbFile = context.getDatabasePath("test.db").apply { + parentFile?.mkdirs() + } + SQLiteDatabase.deleteDatabase(dbFile) + database = SQLiteDatabase.openOrCreateDatabase( + dbFile, + "test-passphrase", + null, + null, + null) + connection = SQLCipherConnection(database) + exec("CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY, v TEXT)") + } + + @After + fun tearDown() { + connection.close() + SQLiteDatabase.deleteDatabase(dbFile) + } + + @Test + fun shouldTrackSessionStateWhenExecutingRawBeginImmediate() { + assertFalse(connection.inTransaction()) + exec("BEGIN IMMEDIATE TRANSACTION") + assertTrue(connection.inTransaction()) + assertTrue(database.inTransaction()) + exec("END TRANSACTION") + assertFalse(connection.inTransaction()) + } + + @Test + fun shouldPersistChangesWhenCommitting() { + exec("BEGIN IMMEDIATE TRANSACTION") + exec("INSERT INTO t (id, v) VALUES (1, 'kept')") + exec("COMMIT") + assertEquals(1L, queryLong("SELECT COUNT(*) FROM t")) + } + + @Test + fun shouldDiscardChangesWhenRollingBack() { + exec("BEGIN IMMEDIATE TRANSACTION") + exec("INSERT INTO t (id, v) VALUES (1, 'discarded')") + exec("ROLLBACK") + assertFalse(connection.inTransaction()) + assertEquals(0L, queryLong("SELECT COUNT(*) FROM t")) + } + + @Test + fun shouldRecognizeBeginRegardlessOfCaseAndLeadingComments() { + exec("-- Room may prepends comments\nbegin immediate transaction") + assertTrue(connection.inTransaction()) + exec("commit") + assertFalse(connection.inTransaction()) + } + + @Test + fun shouldPassSavepointsThroughInsideTransaction() { + exec("BEGIN IMMEDIATE TRANSACTION") + exec("INSERT INTO t (id, v) VALUES (1, 'outer')") + exec("SAVEPOINT sp1") + exec("INSERT INTO t (id, v) VALUES (2, 'inner')") + exec("ROLLBACK TRANSACTION TO SAVEPOINT sp1") + exec("RELEASE SAVEPOINT sp1") + assertTrue("outer transaction should still be open", connection.inTransaction()) + exec("COMMIT") + assertEquals(1L, queryLong("SELECT COUNT(*) FROM t")) + assertEquals(1L, queryLong("SELECT COUNT(*) FROM t WHERE v = 'outer'")) + assertFalse(connection.inTransaction()) + } + + @Test + fun shouldReturnNoRowsWhenSteppingTransactionStatement() { + connection.prepare("BEGIN IMMEDIATE TRANSACTION").use { statement -> + assertFalse("transaction statement should report SQLITE_DONE", statement.step()) + } + exec("ROLLBACK") + } + + @Test + fun shouldEnableWriteAheadLoggingWhenSettingJournalModeWal() { + assertFalse(database.isWriteAheadLoggingEnabled) + connection.prepare("PRAGMA journal_mode = WAL").use { statement -> + assertTrue(statement.step()) + assertEquals("wal", statement.getText(0).lowercase()) + } + assertTrue(database.isWriteAheadLoggingEnabled) + } + + @Test + fun shouldDisableWriteAheadLoggingWhenSettingJournalModeDelete() { + exec("PRAGMA journal_mode = WAL") + assertTrue(database.isWriteAheadLoggingEnabled) + exec("PRAGMA journal_mode = DELETE") + assertFalse(database.isWriteAheadLoggingEnabled) + } + + @Test + fun shouldPreserveTruncateJournalModeAcrossPoolReconfiguration() { + exec("PRAGMA journal_mode = TRUNCATE") + connection.prepare("PRAGMA journal_mode").use { statement -> + assertTrue(statement.step()) + assertEquals("truncate", statement.getText(0).lowercase()) + } + } + + @Test + fun shouldNotToggleWriteAheadLoggingWhenQueryingJournalMode() { + val before = database.isWriteAheadLoggingEnabled + connection.prepare("PRAGMA journal_mode").use { statement -> + assertTrue(statement.step()) + } + assertEquals(before, database.isWriteAheadLoggingEnabled) + } + + @Test + fun shouldRunOtherPragmasOnStandardStatement() { + connection.prepare("PRAGMA user_version").use { statement -> + assertTrue(statement.step()) + assertEquals(0L, statement.getLong(0)) + } + } + + @Test + fun shouldRoundTripDataThroughStandardStatement() { + connection.prepare("INSERT INTO t (id, v) VALUES (?, ?)").use { statement -> + statement.bindLong(1, 42L) + statement.bindText(2, "hello") + assertFalse(statement.step()) + } + connection.prepare("SELECT v FROM t WHERE id = ?").use { statement -> + statement.bindLong(1, 42L) + assertTrue(statement.step()) + assertEquals("hello", statement.getText(0)) + assertFalse(statement.step()) + } + } + + @Test + fun shouldBindAllSupportedTypesAndReadThemBack() { + createUserTable() + val blob = byteArrayOf(0x00, 0x01, -0x80, 0x42) + connection.prepare( + "INSERT INTO user (name, age, email, score, avatar, flag) VALUES (?, ?, ?, ?, ?, ?)" + ).use { stmt -> + stmt.bindText(1, "John Doe") + stmt.bindInt(2, 42) + stmt.bindText(3, "john@doe.com") + stmt.bindDouble(4, 3.14159) + stmt.bindBlob(5, blob) + stmt.bindBoolean(6, true) + assertFalse(stmt.step()) + } + connection.prepare("SELECT id, name, age, email, score, avatar, flag FROM user").use { stmt -> + assertTrue(stmt.step()) + assertEquals(7, stmt.getColumnCount()) + assertTrue(stmt.getLong(0) > 0L) + assertEquals("John Doe", stmt.getText(1)) + assertEquals(42, stmt.getInt(2)) + assertEquals("john@doe.com", stmt.getText(3)) + assertEquals(3.14159, stmt.getDouble(4), 1e-9) + assertArrayEquals(blob, stmt.getBlob(5)) + assertEquals(true, stmt.getBoolean(6)) + + assertEquals("name", stmt.getColumnName(1)) + assertEquals("age", stmt.getColumnName(2)) + assertEquals("email", stmt.getColumnName(3)) + assertEquals("score", stmt.getColumnName(4)) + assertEquals("avatar", stmt.getColumnName(5)) + assertEquals("flag", stmt.getColumnName(6)) + } + } + + @Test + fun shouldRebindStatementAfterResetForStatementReuse() { + createUserTable() + connection.prepare("INSERT INTO user (name) VALUES (?)").use { stmt -> + listOf("one", "two", "three").forEach { name -> + stmt.reset() + stmt.clearBindings() + stmt.bindText(1, name) + assertFalse(stmt.step()) + } + } + connection.prepare("SELECT count(*) FROM user").use { stmt -> + stmt.step() + assertEquals(3L, stmt.getLong(0)) + } + } + + @Test + fun shouldThrowWhenUsingClosedStatement() { + createUserTable() + val stmt = connection.prepare("SELECT count(*) FROM user") + stmt.close() + assertThrows(android.database.SQLException::class.java) { + stmt.step() + } + } + + private fun exec(sql: String) { + connection.prepare(sql).use { it.step() } + } + + private fun queryLong(sql: String): Long = + connection.prepare(sql).use { statement -> + assertTrue("expected a row from: $sql", statement.step()) + statement.getLong(0) + } + + private fun createUserTable(conn: SQLiteConnection = connection) { + connection.execSQL( + """ + CREATE TABLE IF NOT EXISTS user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + age INTEGER, + email TEXT UNIQUE, + score REAL, + avatar BLOB, + flag INTEGER + ) + """.trimIndent() + ) + } +} diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt index 061e794..946d675 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt @@ -12,9 +12,13 @@ import androidx.room3.PrimaryKey import androidx.room3.Query import androidx.room3.Room import androidx.room3.RoomDatabase +import androidx.room3.Upsert +import androidx.room3.withWriteTransaction +import androidx.sqlite.SQLiteConnection import androidx.sqlite.SQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import junit.framework.TestCase.assertEquals import kotlinx.coroutines.test.runTest import org.hamcrest.CoreMatchers.not import org.hamcrest.MatcherAssert.assertThat @@ -27,6 +31,7 @@ import org.junit.Test import org.junit.runner.RunWith import java.io.File import java.nio.charset.StandardCharsets +import kotlin.coroutines.cancellation.CancellationException @RunWith(AndroidJUnit4::class) class SupportAPIRoomTest { @@ -34,6 +39,7 @@ class SupportAPIRoomTest { private lateinit var db: AppDatabase private lateinit var userDao: UserDao private lateinit var databaseFile: File + private lateinit var connection: SQLiteConnection @Before fun setup(){ @@ -53,6 +59,7 @@ class SupportAPIRoomTest { null, null ) + connection = driver.open(databaseFile.absolutePath); db = Room.databaseBuilder( context, AppDatabase::class.java, @@ -62,17 +69,23 @@ class SupportAPIRoomTest { userDao = db.userDao() } + @After + fun after() { + db.close() + databaseFile.delete() + } + @Test fun shouldInsertDataViaDao() { val user = User("John", "Doe") - user.uid = userDao.insert(user) - assertThat(user.uid, not(0L)) + val uid = userDao.insert(user) + assertThat(uid, not(0L)) } @Test @Throws(InterruptedException::class) fun shouldDeleteDataViaDao() = runTest { - val user = User("foo", "bar").apply { uid = 1 } + val user = User(uid = 1, firstName = "foo", lastName = "bar") userDao.insert(user) assertThat(userDao.findById(user.uid), notNullValue()) userDao.delete(user) @@ -81,7 +94,7 @@ class SupportAPIRoomTest { @Test fun shouldQueryDataByParametersViaDao(){ - val user = User("foo", "bar").apply { uid = 1 } + val user = User(uid = 1, firstName = "foo", lastName = "bar") userDao.insert(user) val foundUser = userDao.findByName(user.firstName, user.lastName) assertThat(foundUser, notNullValue()) @@ -90,6 +103,54 @@ class SupportAPIRoomTest { assertThat(foundUser.lastName, `is`(user.lastName)) } + @Test + fun shouldReplaceExistingRowWithReplaceConflictStrategy() = runTest { + val original = User(firstName = "John", lastName = "Doe") + val id = userDao.insert(original) + + val updated = original.copy(uid = id, firstName = "Jane") + userDao.insertOrReplace(updated) + + val all = userDao.all + assertEquals(1, all.size) + with(all.single()) { + assertEquals(id, uid) + assertEquals("Jane", firstName) + assertEquals("Doe", lastName) + } + } + + @Test + fun shouldAllowUpsertBehavior() { + val user = User(firstName = "John", lastName = "Doe") + val uid = userDao.upsert(user) + userDao.upsert(user.copy(uid = uid, firstName = "Jane")) + with(userDao.all.single()) { + assertEquals(uid, uid) + assertEquals("Jane", firstName) + assertEquals("Doe", lastName) + } + } + + @Test + fun shouldRollbackWriteTransactionOnCancellation() = runTest { + try { + db.withWriteTransaction { + userDao.insert(User(firstName = "foo", lastName = "bar")) + throw CancellationException("cancelled mid-transaction") + } + } catch (_: CancellationException) {} + assertEquals(0, userDao.all.count()) + } + + @Test + fun shouldSeeOwnWritesInsideWriteTransaction() = runTest { + db.withWriteTransaction { + userDao.insert(User(firstName = "foo", lastName = "bar")) + assertEquals(1, userDao.all.count()) + } + } + @Test fun shouldSupportChangingPasswordWithRoom() = runTest { val newPassword = "foobar".toByteArray() @@ -102,25 +163,17 @@ class SupportAPIRoomTest { assertThat(userDao.all.count(), `is`(1)) } - @After - fun after() { - db.close() - databaseFile.delete() - } - @Database(entities = [User::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } @Entity - class User( - @field:ColumnInfo(name = "first_name") var firstName: String, - @field:ColumnInfo(name = "last_name") var lastName: String - ) { - @PrimaryKey(autoGenerate = true) - var uid: Long = 0 - } + data class User( + @ColumnInfo(name = "first_name") val firstName: String, + @ColumnInfo(name = "last_name") val lastName: String, + @PrimaryKey(autoGenerate = true) val uid: Long = 0 + ) @Dao interface UserDao { @@ -139,9 +192,15 @@ class SupportAPIRoomTest { @Query("SELECT * FROM user WHERE uid = :userId") fun findById(userId: Long): User? - @Insert(onConflict = OnConflictStrategy.REPLACE) + @Insert fun insert(user: User): Long + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrReplace(user: User): Long + + @Upsert + fun upsert(user: User): Long + @Delete fun delete(user: User) diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/JournalModeStatement.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/JournalModeStatement.java new file mode 100644 index 0000000..3656bdf --- /dev/null +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/JournalModeStatement.java @@ -0,0 +1,116 @@ +package net.zetetic.database.sqlcipher.driver; + +import androidx.annotation.NonNull; +import androidx.sqlite.SQLiteStatement; + +import net.zetetic.database.sqlcipher.SQLiteDatabase; + +class JournalModeStatement implements SQLiteStatement { + + private final SQLiteDatabase database; + private final SQLiteStatement delegate; + + JournalModeStatement( + SQLiteDatabase database, + SQLiteStatement delegate) { + this.database = database; + this.delegate = delegate; + } + + /** + * Perform journal mode get/set operation. When in WAL + * mode, we dispatch to enableWriteAheadLogging which + * will reconfigure the connection pool accordingly. + */ + @Override + public boolean step() { + var hasRow = delegate.step(); + // The pragma returns the mode actually in effect. + if ("wal".equalsIgnoreCase(delegate.getText(0))) { + database.enableWriteAheadLogging(); + } else { + database.disableWriteAheadLogging(); + } + return hasRow; + } + + @Override public void bindBlob( + int index, + @NonNull byte[] value) { + delegate.bindBlob(index, value); + } + + @Override public void bindDouble( + int index, + double value) { + delegate.bindDouble(index, value); + } + + @Override public void bindLong( + int index, + long value) { + delegate.bindLong(index, value); + } + + @Override public void bindText( + int index, + @NonNull String value) { + delegate.bindText(index, value); + } + + @Override public void bindNull( + int index) { + delegate.bindNull(index); + } + + @Override @NonNull public byte[] getBlob( + int index) { + return delegate.getBlob(index); + } + + @Override public double getDouble( + int index) { + return delegate.getDouble(index); + } + + @Override public long getLong( + int index) { + return delegate.getLong(index); + } + + @Override @NonNull public String getText( + int index) { + return delegate.getText(index); + } + + @Override public boolean isNull( + int index) { + return delegate.isNull(index); + } + + @Override public int getColumnCount() { + return delegate.getColumnCount(); + } + + @Override @NonNull public String getColumnName( + int index) { + return delegate.getColumnName(index); + } + + @Override public int getColumnType( + int index) { + return delegate.getColumnType(index); + } + + @Override public void reset() { + delegate.reset(); + } + + @Override public void clearBindings() { + delegate.clearBindings(); + } + + @Override public void close() { + delegate.close(); + } +} diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java index 2e39e54..51de304 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherConnection.java @@ -14,9 +14,25 @@ public SQLCipherConnection( this.database = database; } + /** + * Transform SQL string into SQLiteStatement. We preprocess the SQL with the following rules: + * - When a journal mode is set, dispatch the operation, specifically when WAL is used we + * dispatch using the enableWriteAheadLogging which will reconfigure the underlying + * connection pool. + * - For all other statements, we return a default SQLCipherStatement for processing. + */ @NonNull @Override - public SQLiteStatement prepare(@NonNull String sql) { + public SQLiteStatement prepare( + @NonNull String sql) { + var trimmed = sql.trim(); + var prefix = statementPrefix(trimmed); + if (prefix != null) { + if (isJournalModeSet(prefix, trimmed)) { + // Run the pragma normally, then let SQLiteDatabase reconfigure the pool + return new JournalModeStatement(database, new SQLCipherStatement(database, sql)); + } + } return new SQLCipherStatement(database, sql); } @@ -25,7 +41,8 @@ public boolean inTransaction() { return database.inTransaction(); } - public void changePassword(byte[] newPassword){ + public void changePassword( + byte[] newPassword){ database.changePassword(newPassword); } @@ -33,4 +50,65 @@ public void changePassword(byte[] newPassword){ public void close() { database.close(); } + + static boolean isJournalModeSet( + String prefix, + String sql) { + if (!"PRA".equalsIgnoreCase(prefix)) { + return false; + } + var lower = sql.toLowerCase(); + var index = lower.indexOf("journal_mode"); + // Only a *set* ("PRAGMA journal_mode = WAL") needs special handling; + // a bare query ("PRAGMA journal_mode") can run normally. + return index >= 0 + && lower.indexOf('=', index) >= 0; + } + + /** + * Returns the first 3 significant characters of the statement, skipping + * whitespace and SQL comments (-- line and slash-star block). Port of + * androidx.sqlite.util.SQLStatementParser.getStatementPrefix() + */ + static String statementPrefix( + String statement) { + var limit = statement.length() - 2; + if (limit < 0) { + return null; + } + var index = 0; + while (index >= 0 && index < limit) { + var c = statement.charAt(index); + if (c <= ' ') { + index++; + } else if (c == '-') { + if (statement.charAt(index + 1) != '-') { + break; + } + index = statement.indexOf('\n', index + 2); + if (index < 0) { + return null; + } + index++; + } else if (c == '/') { + if (statement.charAt(index + 1) != '*') { + break; + } + index++; + do { + index = statement.indexOf('*', index + 1); + if (index < 0) { + return null; + } + } while (index + 1 < limit && statement.charAt(index + 1) != '/'); + index += 2; + } else { + break; + } + } + if (index < 0 || index >= statement.length()) { + return null; + } + return statement.substring(index, Math.min(index + 3, statement.length())); + } } diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java index c181c98..2ad3806 100644 --- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java +++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/driver/SQLCipherStatement.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import androidx.sqlite.SQLiteStatement; +import android.database.SQLException; import net.zetetic.database.sqlcipher.SQLiteDatabase; @@ -18,6 +19,7 @@ public class SQLCipherStatement implements SQLiteStatement { private final Map bindings = new LinkedHashMap<>(); private Cursor cursor; private boolean stepped; + private boolean closed; public SQLCipherStatement( SQLiteDatabase database, @@ -118,6 +120,9 @@ public int getColumnType( @Override public boolean step() { + if(closed){ + throw new SQLException("statement is closed"); + } if (cursor == null) { var maxIndex = 0; for (int key : bindings.keySet()) { @@ -148,7 +153,9 @@ public void clearBindings() { } @Override - public void close() {} + public void close() { + closed = true; + } private void createCursor() { var maxIndex = 0; From e5534be386e13fe29c40c273af44b40f2e39efc8 Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Thu, 6 Aug 2026 15:57:07 -0500 Subject: [PATCH 15/17] Add test to verify existing WAL flag in db is reflected correctly --- sqlcipher/build.gradle | 2 ++ .../sqlcipher/driver/SupportAPIRoomTest.kt | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle index 79620f9..1ee6345 100644 --- a/sqlcipher/build.gradle +++ b/sqlcipher/build.gradle @@ -159,6 +159,8 @@ dependencies { def room_version = "3.0.1" androidTestImplementation "androidx.room3:room3-runtime:$room_version" kspAndroidTest "androidx.room3:room3-compiler:$room_version" + // Needed for roomDatabase.getSupportWrapper() tests + androidTestImplementation "androidx.room3:room3-sqlite-wrapper:$room_version" // Needed for runTest androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0" diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt index 946d675..8d8730f 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/SupportAPIRoomTest.kt @@ -13,6 +13,7 @@ import androidx.room3.Query import androidx.room3.Room import androidx.room3.RoomDatabase import androidx.room3.Upsert +import androidx.room3.support.getSupportWrapper import androidx.room3.withWriteTransaction import androidx.sqlite.SQLiteConnection import androidx.sqlite.SQLiteDriver @@ -40,11 +41,11 @@ class SupportAPIRoomTest { private lateinit var userDao: UserDao private lateinit var databaseFile: File private lateinit var connection: SQLiteConnection + private val defaultPassword = "user".toByteArray(StandardCharsets.UTF_8) @Before fun setup(){ - val password = "user".toByteArray(StandardCharsets.UTF_8) - setup(password, true) + setup(defaultPassword, true) } fun setup(password: ByteArray, deleteDatabase: Boolean) { @@ -163,6 +164,18 @@ class SupportAPIRoomTest { assertThat(userDao.all.count(), `is`(1)) } + @Test + fun shouldVerifyWalModeFromExistingDatabase() = runTest { + connection.prepare("PRAGMA journal_mode = wal;").use { stmt -> + stmt.step(); + val mode = stmt.getText(0) + assertThat(mode, `is`("wal")) + } + connection.close() + setup(defaultPassword, false) + assertThat(db.getSupportWrapper().isWriteAheadLoggingEnabled, `is`(true)) + } + @Database(entities = [User::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao From 04d9948eb6a5ca1f9740ae72995a34fd6e11601f Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Tue, 18 Aug 2026 09:04:31 -0500 Subject: [PATCH 16/17] Bump version tests to 4.18.0 --- .../net/zetetic/database/sqlcipher/SQLCipherVersionTest.java | 2 +- .../database/sqlcipher/driver/Room3SQLCipherDriverTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherVersionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherVersionTest.java index 36192b7..7f45579 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherVersionTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/SQLCipherVersionTest.java @@ -17,6 +17,6 @@ public void shouldExtractLibraryCipherVersion() { cipherVersion = cursor.getString(0); cursor.close(); } - assertThat(cipherVersion, containsString("4.17.0")); + assertThat(cipherVersion, containsString("4.18.0")); } } diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java index e7593f7..c3a4e2e 100644 --- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java +++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher/driver/Room3SQLCipherDriverTest.java @@ -59,7 +59,7 @@ public void shouldRetrieveCipherVersion(){ var stmt = connection.prepare("PRAGMA cipher_version;"); stmt.step(); var version = stmt.getText(0); - Assert.assertEquals("4.17.0 community", version); + Assert.assertEquals("4.18.0 community", version); connection.close(); } From e15752c3eb4364e27203185587471aab25b0ca91 Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Tue, 18 Aug 2026 09:05:59 -0500 Subject: [PATCH 17/17] Bump library version to 4.18.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c5d454a..01dd35f 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ project.ext { if(project.hasProperty('sqlcipherAndroidVersion') && "${sqlcipherAndroidVersion}") { libraryVersion = "${sqlcipherAndroidVersion}" } else { - libraryVersion = "4.17.0" + libraryVersion = "4.18.0" } minSdkVersion = 23 androidXSQLiteVersion = "2.7.0"