Loading core/jni/android_database_SQLiteRawStatement.cpp +10 −3 Original line number Original line Diff line number Diff line Loading @@ -41,6 +41,11 @@ */ */ namespace android { namespace android { // A zero-length byte array that can be returned by getColumnBlob(). The theory is that // zero-length blobs are common enough that it is worth having a single, global instance. The // object is created in the jni registration function. It is never destroyed. static jbyteArray emptyArray = nullptr; // Helper functions. // Helper functions. static sqlite3 *db(long statementPtr) { static sqlite3 *db(long statementPtr) { return sqlite3_db_handle(reinterpret_cast<sqlite3_stmt*>(statementPtr)); return sqlite3_db_handle(reinterpret_cast<sqlite3_stmt*>(statementPtr)); Loading Loading @@ -226,7 +231,7 @@ static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) { throwIfInvalidColumn(env, stmtPtr, col); throwIfInvalidColumn(env, stmtPtr, col); const void* blob = sqlite3_column_blob(stmt(stmtPtr), col); const void* blob = sqlite3_column_blob(stmt(stmtPtr), col); if (blob == nullptr) { if (blob == nullptr) { return NULL; return (sqlite3_column_type(stmt(stmtPtr), col) == SQLITE_NULL) ? NULL : emptyArray; } } size_t size = sqlite3_column_bytes(stmt(stmtPtr), col); size_t size = sqlite3_column_bytes(stmt(stmtPtr), col); jbyteArray result = env->NewByteArray(size); jbyteArray result = env->NewByteArray(size); Loading Loading @@ -316,8 +321,10 @@ static const JNINativeMethod sStatementMethods[] = int register_android_database_SQLiteRawStatement(JNIEnv *env) int register_android_database_SQLiteRawStatement(JNIEnv *env) { { return RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement", RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement", sStatementMethods, NELEM(sStatementMethods)); sStatementMethods, NELEM(sStatementMethods)); emptyArray = MakeGlobalRefOrDie(env, env->NewByteArray(0)); return 0; } } } // namespace android } // namespace android core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java +95 −0 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.database.sqlite; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assert.fail; Loading Loading @@ -507,6 +508,12 @@ public class SQLiteRawStatementTest { s.bindInt(1, 3); s.bindInt(1, 3); s.step(); s.step(); s.reset(); s.reset(); // Bind a zero-length blob s.clearBindings(); s.bindInt(1, 4); s.bindBlob(2, new byte[0]); s.step(); s.reset(); } } mDatabase.setTransactionSuccessful(); mDatabase.setTransactionSuccessful(); } finally { } finally { Loading Loading @@ -545,6 +552,17 @@ public class SQLiteRawStatementTest { for (int i = 0; i < c.length; i++) c[i] = 0; for (int i = 0; i < c.length; i++) c[i] = 0; s.bindInt(1, 3); s.bindInt(1, 3); assertTrue(s.step()); assertTrue(s.step()); assertNull(s.getColumnBlob(0)); assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); s.reset(); // Fetch the zero-length blob s.bindInt(1, 4); assertTrue(s.step()); byte[] r = s.getColumnBlob(0); assertNotNull(r); assertEquals(0, r.length); assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); s.reset(); s.reset(); Loading @@ -571,6 +589,83 @@ public class SQLiteRawStatementTest { } } } } @Test public void testText() { mDatabase.beginTransaction(); try { final String query = "CREATE TABLE t1 (i int, b text)"; try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { assertFalse(s.step()); } mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } // Insert data into the table. mDatabase.beginTransaction(); try { final String query = "INSERT INTO t1 (i, b) VALUES (?1, ?2)"; try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { // Bind a string s.bindInt(1, 1); s.bindText(2, "text"); s.step(); s.reset(); s.clearBindings(); // Bind a zero-length string s.bindInt(1, 2); s.bindText(2, ""); s.step(); s.reset(); s.clearBindings(); // Bind a null string s.clearBindings(); s.bindInt(1, 3); s.step(); s.reset(); s.clearBindings(); } mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } // Read back data and verify it against the reference copy. mDatabase.beginTransactionReadOnly(); try { final String query = "SELECT (b) FROM t1 WHERE i = ?1"; try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { // Fetch the entire reference array. s.bindInt(1, 1); assertTrue(s.step()); assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_TEXT, s.getColumnType(0)); String a = s.getColumnText(0); assertNotNull(a); assertEquals(a, "text"); s.reset(); s.bindInt(1, 2); assertTrue(s.step()); String b = s.getColumnText(0); assertNotNull(b); assertEquals(b, ""); s.reset(); s.bindInt(1, 3); assertTrue(s.step()); String c = s.getColumnText(0); assertNull(c); s.reset(); } } finally { mDatabase.endTransaction(); } } @Test @Test public void testParameterMetadata() { public void testParameterMetadata() { createComplexDatabase(); createComplexDatabase(); Loading Loading
core/jni/android_database_SQLiteRawStatement.cpp +10 −3 Original line number Original line Diff line number Diff line Loading @@ -41,6 +41,11 @@ */ */ namespace android { namespace android { // A zero-length byte array that can be returned by getColumnBlob(). The theory is that // zero-length blobs are common enough that it is worth having a single, global instance. The // object is created in the jni registration function. It is never destroyed. static jbyteArray emptyArray = nullptr; // Helper functions. // Helper functions. static sqlite3 *db(long statementPtr) { static sqlite3 *db(long statementPtr) { return sqlite3_db_handle(reinterpret_cast<sqlite3_stmt*>(statementPtr)); return sqlite3_db_handle(reinterpret_cast<sqlite3_stmt*>(statementPtr)); Loading Loading @@ -226,7 +231,7 @@ static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) { throwIfInvalidColumn(env, stmtPtr, col); throwIfInvalidColumn(env, stmtPtr, col); const void* blob = sqlite3_column_blob(stmt(stmtPtr), col); const void* blob = sqlite3_column_blob(stmt(stmtPtr), col); if (blob == nullptr) { if (blob == nullptr) { return NULL; return (sqlite3_column_type(stmt(stmtPtr), col) == SQLITE_NULL) ? NULL : emptyArray; } } size_t size = sqlite3_column_bytes(stmt(stmtPtr), col); size_t size = sqlite3_column_bytes(stmt(stmtPtr), col); jbyteArray result = env->NewByteArray(size); jbyteArray result = env->NewByteArray(size); Loading Loading @@ -316,8 +321,10 @@ static const JNINativeMethod sStatementMethods[] = int register_android_database_SQLiteRawStatement(JNIEnv *env) int register_android_database_SQLiteRawStatement(JNIEnv *env) { { return RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement", RegisterMethodsOrDie(env, "android/database/sqlite/SQLiteRawStatement", sStatementMethods, NELEM(sStatementMethods)); sStatementMethods, NELEM(sStatementMethods)); emptyArray = MakeGlobalRefOrDie(env, env->NewByteArray(0)); return 0; } } } // namespace android } // namespace android
core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java +95 −0 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.database.sqlite; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assert.fail; Loading Loading @@ -507,6 +508,12 @@ public class SQLiteRawStatementTest { s.bindInt(1, 3); s.bindInt(1, 3); s.step(); s.step(); s.reset(); s.reset(); // Bind a zero-length blob s.clearBindings(); s.bindInt(1, 4); s.bindBlob(2, new byte[0]); s.step(); s.reset(); } } mDatabase.setTransactionSuccessful(); mDatabase.setTransactionSuccessful(); } finally { } finally { Loading Loading @@ -545,6 +552,17 @@ public class SQLiteRawStatementTest { for (int i = 0; i < c.length; i++) c[i] = 0; for (int i = 0; i < c.length; i++) c[i] = 0; s.bindInt(1, 3); s.bindInt(1, 3); assertTrue(s.step()); assertTrue(s.step()); assertNull(s.getColumnBlob(0)); assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); s.reset(); // Fetch the zero-length blob s.bindInt(1, 4); assertTrue(s.step()); byte[] r = s.getColumnBlob(0); assertNotNull(r); assertEquals(0, r.length); assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); assertEquals(0, s.readColumnBlob(0, c, 0, c.length, 0)); for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); for (int i = 0; i < c.length; i++) assertEquals(0, c[i]); s.reset(); s.reset(); Loading @@ -571,6 +589,83 @@ public class SQLiteRawStatementTest { } } } } @Test public void testText() { mDatabase.beginTransaction(); try { final String query = "CREATE TABLE t1 (i int, b text)"; try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { assertFalse(s.step()); } mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } // Insert data into the table. mDatabase.beginTransaction(); try { final String query = "INSERT INTO t1 (i, b) VALUES (?1, ?2)"; try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { // Bind a string s.bindInt(1, 1); s.bindText(2, "text"); s.step(); s.reset(); s.clearBindings(); // Bind a zero-length string s.bindInt(1, 2); s.bindText(2, ""); s.step(); s.reset(); s.clearBindings(); // Bind a null string s.clearBindings(); s.bindInt(1, 3); s.step(); s.reset(); s.clearBindings(); } mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } // Read back data and verify it against the reference copy. mDatabase.beginTransactionReadOnly(); try { final String query = "SELECT (b) FROM t1 WHERE i = ?1"; try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) { // Fetch the entire reference array. s.bindInt(1, 1); assertTrue(s.step()); assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_TEXT, s.getColumnType(0)); String a = s.getColumnText(0); assertNotNull(a); assertEquals(a, "text"); s.reset(); s.bindInt(1, 2); assertTrue(s.step()); String b = s.getColumnText(0); assertNotNull(b); assertEquals(b, ""); s.reset(); s.bindInt(1, 3); assertTrue(s.step()); String c = s.getColumnText(0); assertNull(c); s.reset(); } } finally { mDatabase.endTransaction(); } } @Test @Test public void testParameterMetadata() { public void testParameterMetadata() { createComplexDatabase(); createComplexDatabase(); Loading