Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19cf445a authored by Lee Shombert's avatar Lee Shombert
Browse files

Distinguish null blobs in SQLiteRawStatement

The sqlite value API sqlite3_column_blob() returns a null if the
column type is SQLITE_NULL or a zero-length SQLITE_BLOB.
SQLiteRawStatement.getColumnBlob() now distinguishes between these two
cases: if the column type is SQLITE_NULL, return null, otherwise
return a byte array of length zero.

New unit tests have been added.  Unit tests have also been added for
SQLiteRawStatement.getColumnText()'s handling of empty strings and
null values.

Test: atest
 * FrameworksCoreTests:android.database
 * CtsDatabaseTestCases

Flag: EXEMPT bugfix
Bug: 342687891
Change-Id: Ia041eeb761267de0a4b59af3ddcba6433b231bbb
parent 8ed84947
Loading
Loading
Loading
Loading
+10 −3
Original line number Original line Diff line number Diff line
@@ -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));
@@ -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);
@@ -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
+95 −0
Original line number Original line Diff line number Diff line
@@ -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;


@@ -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 {
@@ -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();
@@ -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();