Skip to content

Commit 6494035

Browse files
Add support for rawExecSQL, add tests for rawQuery binding, fts5, rtree, soundex, savepoint
1 parent 20a8f57 commit 6494035

File tree

4 files changed

+240
-6
lines changed

4 files changed

+240
-6
lines changed

sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java

Lines changed: 166 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import static org.hamcrest.core.Is.is;
55
import static org.hamcrest.core.IsNull.nullValue;
66

7+
import android.content.ContentValues;
78
import android.database.Cursor;
89

910
import net.zetetic.database.sqlcipher.SQLiteDatabase;
1011
import net.zetetic.database.sqlcipher.SQLiteDatabaseConfiguration;
1112
import net.zetetic.database.sqlcipher.SQLiteDatabaseCorruptException;
1213
import net.zetetic.database.sqlcipher.SQLiteException;
14+
import net.zetetic.database.sqlcipher.SQLiteStatement;
1315

16+
import org.hamcrest.core.Is;
1417
import org.junit.Test;
1518

1619
import java.io.File;
@@ -98,15 +101,15 @@ public void testOpenDatabaseConnectionWithInvalidByteArrayPassword() {
98101
}
99102

100103
@Test
101-
public void openExistingSQLCipherDatabaseWithStringPassword(){
104+
public void openExistingSQLCipherDatabaseWithStringPassword() {
102105
File databasePath = null;
103106
int a = 0, b = 0;
104107
try {
105108
closeAndDelete(database);
106109
databasePath = extractAssetToDatabaseDirectory("sqlcipher-4.x-testkey.db");
107110
database = SQLiteDatabase.openDatabase(databasePath.getPath(), "testkey", null, SQLiteDatabase.OPEN_READWRITE, null);
108111
Cursor cursor = database.rawQuery("SELECT * FROM t1;");
109-
if(cursor != null && cursor.moveToFirst()){
112+
if (cursor != null && cursor.moveToFirst()) {
110113
a = cursor.getInt(0);
111114
b = cursor.getInt(1);
112115
cursor.close();
@@ -119,15 +122,15 @@ public void openExistingSQLCipherDatabaseWithStringPassword(){
119122
}
120123

121124
@Test
122-
public void openExistingSQLCipherDatabaseWithByteArrayPassword(){
125+
public void openExistingSQLCipherDatabaseWithByteArrayPassword() {
123126
File databasePath = null;
124127
int a = 0, b = 0;
125128
try {
126129
closeAndDelete(database);
127130
databasePath = extractAssetToDatabaseDirectory("sqlcipher-4.x-testkey.db");
128131
database = SQLiteDatabase.openDatabase(databasePath.getPath(), "testkey".getBytes(StandardCharsets.UTF_8), null, SQLiteDatabase.OPEN_READWRITE, null);
129132
Cursor cursor = database.rawQuery("SELECT * FROM t1;");
130-
if(cursor != null && cursor.moveToFirst()){
133+
if (cursor != null && cursor.moveToFirst()) {
131134
a = cursor.getInt(0);
132135
b = cursor.getInt(1);
133136
cursor.close();
@@ -140,15 +143,15 @@ public void openExistingSQLCipherDatabaseWithByteArrayPassword(){
140143
}
141144

142145
@Test
143-
public void openExistingSQLitePlaintextDatabase(){
146+
public void openExistingSQLitePlaintextDatabase() {
144147
File databasePath = null;
145148
int a = 0, b = 0;
146149
try {
147150
closeAndDelete(database);
148151
databasePath = extractAssetToDatabaseDirectory("sqlite-plaintext.db");
149152
database = SQLiteDatabase.openDatabase(databasePath.getPath(), "", null, SQLiteDatabase.OPEN_READWRITE, null);
150153
Cursor cursor = database.rawQuery("SELECT * FROM t1;");
151-
if(cursor != null && cursor.moveToFirst()){
154+
if (cursor != null && cursor.moveToFirst()) {
152155
a = cursor.getInt(0);
153156
b = cursor.getInt(1);
154157
cursor.close();
@@ -237,4 +240,161 @@ public void shouldThrowExceptionOnChangePasswordWithInMemoryDatabase() {
237240
database = SQLiteDatabase.openOrCreateDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, "foo", null, null, null);
238241
database.changePassword("bar");
239242
}
243+
244+
@Test
245+
public void shouldPerformRawQueryWithBoolean() {
246+
boolean a = false, b = true;
247+
database.execSQL("create table t1(a,b);");
248+
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{true, false});
249+
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", false);
250+
if (cursor != null && cursor.moveToFirst()) {
251+
a = cursor.getInt(0) > 0;
252+
b = cursor.getInt(1) > 0;
253+
}
254+
assertThat(a, is(true));
255+
assertThat(b, is(false));
256+
}
257+
258+
@Test
259+
public void shouldPerformRawQueryWithByteArray() {
260+
byte[] a = generateRandomBytes(64);
261+
byte[] b = generateRandomBytes(64);
262+
byte[] aActual = null, bActual = null;
263+
database.execSQL("create table t1(a,b);");
264+
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
265+
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
266+
if (cursor != null && cursor.moveToFirst()) {
267+
aActual = cursor.getBlob(0);
268+
bActual = cursor.getBlob(1);
269+
}
270+
assertThat(aActual, is(a));
271+
assertThat(bActual, is(b));
272+
}
273+
274+
@Test
275+
public void shouldPerformRawQueryWithDouble() {
276+
double a = 3.14d, b = 42.0d;
277+
double aActual = 0.0d, bActual = 0.0d;
278+
database.execSQL("create table t1(a,b);");
279+
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
280+
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
281+
if (cursor != null && cursor.moveToFirst()) {
282+
aActual = cursor.getDouble(0);
283+
bActual = cursor.getDouble(1);
284+
}
285+
assertThat(aActual, is(a));
286+
assertThat(bActual, is(b));
287+
}
288+
289+
@Test
290+
public void shouldPerformRawQueryWithFloat() {
291+
float a = 3.14f, b = 42.0f;
292+
float aActual = 0.0f, bActual = 0.0f;
293+
database.execSQL("create table t1(a,b);");
294+
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
295+
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
296+
if (cursor != null && cursor.moveToFirst()) {
297+
aActual = cursor.getFloat(0);
298+
bActual = cursor.getFloat(1);
299+
}
300+
assertThat(aActual, is(a));
301+
assertThat(bActual, is(b));
302+
}
303+
304+
@Test
305+
public void shouldPerformRawQueryWithLong() {
306+
long a = 3L, b = 42L;
307+
long aActual = 0L, bActual = 0L;
308+
database.execSQL("create table t1(a,b);");
309+
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
310+
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
311+
if (cursor != null && cursor.moveToFirst()) {
312+
aActual = cursor.getLong(0);
313+
bActual = cursor.getLong(1);
314+
}
315+
assertThat(aActual, is(a));
316+
assertThat(bActual, is(b));
317+
}
318+
319+
@Test
320+
public void shouldPerformRawQueryWithString() {
321+
String a = "one for the money", b = "two for the show";
322+
String aActual = "", bActual = "";
323+
database.execSQL("create table t1(a,b);");
324+
database.execSQL("insert into t1(a,b) values(?, ?);", new Object[]{a, b});
325+
Cursor cursor = database.rawQuery("select * from t1 where b = ?;", b);
326+
if (cursor != null && cursor.moveToFirst()) {
327+
aActual = cursor.getString(0);
328+
bActual = cursor.getString(1);
329+
}
330+
assertThat(aActual, is(a));
331+
assertThat(bActual, is(b));
332+
}
333+
334+
@Test
335+
public void shouldPerformFTS5Search() {
336+
boolean found = false;
337+
database.execSQL("CREATE VIRTUAL TABLE email USING fts5(sender, title, body);");
338+
database.execSQL("insert into email(sender, title, body) values(?, ?, ?);",
339+
new Object[]{"foo@bar.com", "Test Email", "This is a test email message."});
340+
Cursor cursor = database.rawQuery("select * from email where email match ?;", "test");
341+
if (cursor != null && cursor.moveToFirst()) {
342+
found = cursor.getString(cursor.getColumnIndex("sender")).equals("foo@bar.com");
343+
}
344+
assertThat(found, is(true));
345+
}
346+
347+
@Test
348+
public void shouldPerformRTreeTest() {
349+
int id = 0;
350+
String create = "CREATE VIRTUAL TABLE demo_index USING rtree(id, minX, maxX, minY, maxY);";
351+
String insert = "INSERT INTO demo_index VALUES(?, ?, ?, ?, ?);";
352+
database.execSQL(create);
353+
database.execSQL(insert, new Object[]{1, -80.7749, -80.7747, 35.3776, 35.3778});
354+
Cursor cursor = database.rawQuery("SELECT * FROM demo_index WHERE maxY < ?;", 36);
355+
if (cursor != null && cursor.moveToNext()) {
356+
id = cursor.getInt(0);
357+
cursor.close();
358+
}
359+
assertThat(id, is(1));
360+
}
361+
362+
@Test
363+
public void shouldPerformSoundexTest() {
364+
String value = "";
365+
Cursor cursor = database.rawQuery("SELECT soundex('sqlcipher');");
366+
if (cursor != null && cursor.moveToFirst()) {
367+
value = cursor.getString(0);
368+
cursor.close();
369+
}
370+
assertThat(value, is("S421"));
371+
}
372+
373+
@Test
374+
public void shouldInsertWithOnConflictTest(){
375+
database.execSQL("create table user(_id integer primary key autoincrement, email text unique not null);");
376+
ContentValues values = new ContentValues();
377+
values.put("email", "foo@bar.com");
378+
long id = database.insertWithOnConflict("user", null, values,
379+
SQLiteDatabase.CONFLICT_IGNORE);
380+
long error = database.insertWithOnConflict("user", null, values,
381+
SQLiteDatabase.CONFLICT_IGNORE);
382+
assertThat(id, is(1L));
383+
assertThat(error, is(-1L));
384+
}
385+
386+
@Test
387+
public void shouldPerformRollbackToSavepoint(){
388+
database.rawExecSQL("savepoint foo;");
389+
database.rawExecSQL("create table t1(a,b);");
390+
database.rawExecSQL("insert into t1(a,b) values(?,?);", "one for the money", "two for the show");
391+
database.rawExecSQL("savepoint bar;");
392+
database.rawExecSQL("insert into t1(a,b) values(?,?);", "three to get ready", "go man go");
393+
database.rawExecSQL("rollback transaction to bar;");
394+
database.rawExecSQL("commit;");
395+
SQLiteStatement statement = database.compileStatement("select count(*) from t1 where a = ?;");
396+
statement.bindString(1, "one for the money");
397+
long count = statement.simpleQueryForLong();
398+
assertThat(count, is(1L));
399+
}
240400
}

sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,28 @@ public void execSQL(String sql, Object[] bindArgs) throws SQLException {
18561856
executeSql(sql, bindArgs);
18571857
}
18581858

1859+
/**
1860+
* Executes a statement that returns a count of the number of rows
1861+
* that were changed. No transaction state checking is performed.
1862+
* @param sql The SQL statement to execute.
1863+
* @param bindArgs The arguments to bind.
1864+
* @return The number of rows that were changed.
1865+
*/
1866+
public int rawExecSQL(String sql, Object...bindArgs) throws SQLException {
1867+
acquireReference();
1868+
try {
1869+
SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
1870+
try {
1871+
return statement.executeUpdateDeleteRaw();
1872+
} finally {
1873+
statement.close();
1874+
}
1875+
1876+
} finally {
1877+
releaseReference();
1878+
}
1879+
}
1880+
18591881
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
18601882
acquireReference();
18611883
try {

sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteSession.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,37 @@ public int executeForChangedRowCount(String sql, Object[] bindArgs, int connecti
762762
}
763763
}
764764

765+
/**
766+
* Executes a statement that returns a count of the number of rows
767+
* that were changed. Use for UPDATE or DELETE SQL statements. Does not
768+
* perform additional transaction process verification.
769+
*
770+
* @param sql The SQL statement to execute.
771+
* @param bindArgs The arguments to bind, or null if none.
772+
* @param connectionFlags The connection flags to use if a connection must be
773+
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
774+
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
775+
* @return The number of rows that were changed.
776+
*
777+
* @throws SQLiteException if an error occurs, such as a syntax error
778+
* or invalid number of bind arguments.
779+
* @throws OperationCanceledException if the operation was canceled.
780+
*/
781+
public int executeForChangedRowCountRaw(String sql, Object[] bindArgs, int connectionFlags,
782+
CancellationSignal cancellationSignal) {
783+
if (sql == null) {
784+
throw new IllegalArgumentException("sql must not be null.");
785+
}
786+
787+
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
788+
try {
789+
return mConnection.executeForChangedRowCount(sql, bindArgs,
790+
cancellationSignal); // might throw
791+
} finally {
792+
releaseConnection(); // might throw
793+
}
794+
}
795+
765796
/**
766797
* Executes a statement that returns the row id of the last row inserted
767798
* by the statement. Use for INSERT SQL statements.

sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteStatement.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,27 @@ public int executeUpdateDelete() {
7575
}
7676
}
7777

78+
/**
79+
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
80+
* statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
81+
* No transaction state checking is performed.
82+
* @return the number of rows affected by this SQL statement execution.
83+
* @throws android.database.SQLException If the SQL string is invalid for
84+
* some reason
85+
*/
86+
public int executeUpdateDeleteRaw() {
87+
acquireReference();
88+
try {
89+
return getSession().executeForChangedRowCountRaw(
90+
getSql(), getBindArgs(), getConnectionFlags(), null);
91+
} catch (SQLiteDatabaseCorruptException ex) {
92+
onCorruption();
93+
throw ex;
94+
} finally {
95+
releaseReference();
96+
}
97+
}
98+
7899
/**
79100
* Execute this SQL statement and return the ID of the row inserted due to this call.
80101
* The SQL statement should be an INSERT for this to be a useful call.

0 commit comments

Comments
 (0)