Add support for rawExecSQL, add tests for rawQuery binding, fts5, rtree, soundex, savepoint

This commit is contained in:
Nick Parker 2021-08-10 16:53:47 -05:00
parent 20a8f57163
commit 6494035b17
4 changed files with 240 additions and 6 deletions

View File

@ -4,13 +4,16 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import android.content.ContentValues;
import android.database.Cursor;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDatabaseConfiguration;
import net.zetetic.database.sqlcipher.SQLiteDatabaseCorruptException;
import net.zetetic.database.sqlcipher.SQLiteException;
import net.zetetic.database.sqlcipher.SQLiteStatement;
import org.hamcrest.core.Is;
import org.junit.Test;
import java.io.File;
@ -98,7 +101,7 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
}
@Test
public void openExistingSQLCipherDatabaseWithStringPassword(){
public void openExistingSQLCipherDatabaseWithStringPassword() {
File databasePath = null;
int a = 0, b = 0;
try {
@ -106,7 +109,7 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
databasePath = extractAssetToDatabaseDirectory("sqlcipher-4.x-testkey.db");
database = SQLiteDatabase.openDatabase(databasePath.getPath(), "testkey", null, SQLiteDatabase.OPEN_READWRITE, null);
Cursor cursor = database.rawQuery("SELECT * FROM t1;");
if(cursor != null && cursor.moveToFirst()){
if (cursor != null && cursor.moveToFirst()) {
a = cursor.getInt(0);
b = cursor.getInt(1);
cursor.close();
@ -119,7 +122,7 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
}
@Test
public void openExistingSQLCipherDatabaseWithByteArrayPassword(){
public void openExistingSQLCipherDatabaseWithByteArrayPassword() {
File databasePath = null;
int a = 0, b = 0;
try {
@ -127,7 +130,7 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
databasePath = extractAssetToDatabaseDirectory("sqlcipher-4.x-testkey.db");
database = SQLiteDatabase.openDatabase(databasePath.getPath(), "testkey".getBytes(StandardCharsets.UTF_8), null, SQLiteDatabase.OPEN_READWRITE, null);
Cursor cursor = database.rawQuery("SELECT * FROM t1;");
if(cursor != null && cursor.moveToFirst()){
if (cursor != null && cursor.moveToFirst()) {
a = cursor.getInt(0);
b = cursor.getInt(1);
cursor.close();
@ -140,7 +143,7 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
}
@Test
public void openExistingSQLitePlaintextDatabase(){
public void openExistingSQLitePlaintextDatabase() {
File databasePath = null;
int a = 0, b = 0;
try {
@ -148,7 +151,7 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
databasePath = extractAssetToDatabaseDirectory("sqlite-plaintext.db");
database = SQLiteDatabase.openDatabase(databasePath.getPath(), "", null, SQLiteDatabase.OPEN_READWRITE, null);
Cursor cursor = database.rawQuery("SELECT * FROM t1;");
if(cursor != null && cursor.moveToFirst()){
if (cursor != null && cursor.moveToFirst()) {
a = cursor.getInt(0);
b = cursor.getInt(1);
cursor.close();
@ -237,4 +240,161 @@ public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
database = SQLiteDatabase.openOrCreateDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, "foo", null, null, null);
database.changePassword("bar");
}
@Test
public void shouldPerformRawQueryWithBoolean() {
boolean a = false, b = true;
database.execSQL("create table t1(a,b);");
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{true, false});
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", false);
if (cursor != null && cursor.moveToFirst()) {
a = cursor.getInt(0) > 0;
b = cursor.getInt(1) > 0;
}
assertThat(a, is(true));
assertThat(b, is(false));
}
@Test
public void shouldPerformRawQueryWithByteArray() {
byte[] a = generateRandomBytes(64);
byte[] b = generateRandomBytes(64);
byte[] aActual = null, bActual = null;
database.execSQL("create table t1(a,b);");
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
if (cursor != null && cursor.moveToFirst()) {
aActual = cursor.getBlob(0);
bActual = cursor.getBlob(1);
}
assertThat(aActual, is(a));
assertThat(bActual, is(b));
}
@Test
public void shouldPerformRawQueryWithDouble() {
double a = 3.14d, b = 42.0d;
double aActual = 0.0d, bActual = 0.0d;
database.execSQL("create table t1(a,b);");
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
if (cursor != null && cursor.moveToFirst()) {
aActual = cursor.getDouble(0);
bActual = cursor.getDouble(1);
}
assertThat(aActual, is(a));
assertThat(bActual, is(b));
}
@Test
public void shouldPerformRawQueryWithFloat() {
float a = 3.14f, b = 42.0f;
float aActual = 0.0f, bActual = 0.0f;
database.execSQL("create table t1(a,b);");
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
if (cursor != null && cursor.moveToFirst()) {
aActual = cursor.getFloat(0);
bActual = cursor.getFloat(1);
}
assertThat(aActual, is(a));
assertThat(bActual, is(b));
}
@Test
public void shouldPerformRawQueryWithLong() {
long a = 3L, b = 42L;
long aActual = 0L, bActual = 0L;
database.execSQL("create table t1(a,b);");
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
if (cursor != null && cursor.moveToFirst()) {
aActual = cursor.getLong(0);
bActual = cursor.getLong(1);
}
assertThat(aActual, is(a));
assertThat(bActual, is(b));
}
@Test
public void shouldPerformRawQueryWithString() {
String a = "one for the money", b = "two for the show";
String aActual = "", bActual = "";
database.execSQL("create table t1(a,b);");
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
if (cursor != null && cursor.moveToFirst()) {
aActual = cursor.getString(0);
bActual = cursor.getString(1);
}
assertThat(aActual, is(a));
assertThat(bActual, is(b));
}
@Test
public void shouldPerformFTS5Search() {
boolean found = false;
database.execSQL("CREATE VIRTUAL TABLE email USING fts5(sender, title, body);");
database.execSQL("insert into email(sender, title, body) values(?, ?, ?);",
new Object[]{"foo@bar.com", "Test Email", "This is a test email message."});
Cursor cursor = database.rawQuery("select * from email where email match ?;", "test");
if (cursor != null && cursor.moveToFirst()) {
found = cursor.getString(cursor.getColumnIndex("sender")).equals("foo@bar.com");
}
assertThat(found, is(true));
}
@Test
public void shouldPerformRTreeTest() {
int id = 0;
String create = "CREATE VIRTUAL TABLE demo_index USING rtree(id, minX, maxX, minY, maxY);";
String insert = "INSERT INTO demo_index VALUES(?, ?, ?, ?, ?);";
database.execSQL(create);
database.execSQL(insert, new Object[]{1, -80.7749, -80.7747, 35.3776, 35.3778});
Cursor cursor = database.rawQuery("SELECT * FROM demo_index WHERE maxY < ?;", 36);
if (cursor != null && cursor.moveToNext()) {
id = cursor.getInt(0);
cursor.close();
}
assertThat(id, is(1));
}
@Test
public void shouldPerformSoundexTest() {
String value = "";
Cursor cursor = database.rawQuery("SELECT soundex('sqlcipher');");
if (cursor != null && cursor.moveToFirst()) {
value = cursor.getString(0);
cursor.close();
}
assertThat(value, is("S421"));
}
@Test
public void shouldInsertWithOnConflictTest(){
database.execSQL("create table user(_id integer primary key autoincrement, email text unique not null);");
ContentValues values = new ContentValues();
values.put("email", "foo@bar.com");
long id = database.insertWithOnConflict("user", null, values,
SQLiteDatabase.CONFLICT_IGNORE);
long error = database.insertWithOnConflict("user", null, values,
SQLiteDatabase.CONFLICT_IGNORE);
assertThat(id, is(1L));
assertThat(error, is(-1L));
}
@Test
public void shouldPerformRollbackToSavepoint(){
database.rawExecSQL("savepoint foo;");
database.rawExecSQL("create table t1(a,b);");
database.rawExecSQL("insert into t1(a,b) values(?,?);", "one for the money", "two for the show");
database.rawExecSQL("savepoint bar;");
database.rawExecSQL("insert into t1(a,b) values(?,?);", "three to get ready", "go man go");
database.rawExecSQL("rollback transaction to bar;");
database.rawExecSQL("commit;");
SQLiteStatement statement = database.compileStatement("select count(*) from t1 where a = ?;");
statement.bindString(1, "one for the money");
long count = statement.simpleQueryForLong();
assertThat(count, is(1L));
}
}

View File

@ -1856,6 +1856,28 @@ public final class SQLiteDatabase extends SQLiteClosable {
executeSql(sql, bindArgs);
}
/**
* Executes a statement that returns a count of the number of rows
* that were changed. No transaction state checking is performed.
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind.
* @return The number of rows that were changed.
*/
public int rawExecSQL(String sql, Object...bindArgs) throws SQLException {
acquireReference();
try {
SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
try {
return statement.executeUpdateDeleteRaw();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
acquireReference();
try {

View File

@ -762,6 +762,37 @@ public final class SQLiteSession {
}
}
/**
* Executes a statement that returns a count of the number of rows
* that were changed. Use for UPDATE or DELETE SQL statements. Does not
* perform additional transaction process verification.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The number of rows that were changed.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public int executeForChangedRowCountRaw(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForChangedRowCount(sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement that returns the row id of the last row inserted
* by the statement. Use for INSERT SQL statements.

View File

@ -75,6 +75,27 @@ public final class SQLiteStatement extends SQLiteProgram {
}
}
/**
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
* statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
* No transaction state checking is performed.
* @return the number of rows affected by this SQL statement execution.
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public int executeUpdateDeleteRaw() {
acquireReference();
try {
return getSession().executeForChangedRowCountRaw(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
/**
* Execute this SQL statement and return the ID of the row inserted due to this call.
* The SQL statement should be an INSERT for this to be a useful call.