summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/database/sqlite/SQLiteRawStatement.java14
-rw-r--r--core/jni/android_database_SQLiteRawStatement.cpp39
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java27
3 files changed, 63 insertions, 17 deletions
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index c59d3cea0414..3f3e46b4334c 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -554,10 +554,16 @@ public final class SQLiteRawStatement implements Closeable {
*
* @see <a href="http://sqlite.org/c3ref/column_blob.html">sqlite3_column_type</a>
*
+ * If the row has no data then a {@link SQLiteMisuseException} is thrown. This condition can
+ * occur the last call to {@link #step()} returned false or if {@link #step()} was not called
+ * before the statement was created or after the last call to {@link #reset()}. Note that
+ * {@link SQLiteMisuseException} may be thrown for other reasons.
+ *
* @param columnIndex The index of a column in the result row. It is zero-based.
* @return The type of the value in the column of the result row.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data.
* @throws SQLiteException if a native error occurs.
*/
@SQLiteDataType
@@ -580,6 +586,7 @@ public final class SQLiteRawStatement implements Closeable {
* @return The name of the column in the result row.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteOutOfMemoryException if the database cannot allocate memory for the name.
*/
@NonNull
@@ -606,6 +613,7 @@ public final class SQLiteRawStatement implements Closeable {
* @return The length, in bytes, of the value in the column.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteException if a native error occurs.
*/
public int getColumnLength(int columnIndex) {
@@ -631,6 +639,7 @@ public final class SQLiteRawStatement implements Closeable {
* @return The value of the column as a blob, or null if the column is NULL.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteException if a native error occurs.
*/
@Nullable
@@ -664,6 +673,7 @@ public final class SQLiteRawStatement implements Closeable {
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws IllegalArgumentException if the buffer is too small for offset+length.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteException if a native error occurs.
*/
public int readColumnBlob(int columnIndex, @NonNull byte[] buffer, int offset,
@@ -691,6 +701,7 @@ public final class SQLiteRawStatement implements Closeable {
* @return The value of a column as a double.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteException if a native error occurs.
*/
public double getColumnDouble(int columnIndex) {
@@ -715,6 +726,7 @@ public final class SQLiteRawStatement implements Closeable {
* @return The value of the column as an int.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteException if a native error occurs.
*/
public int getColumnInt(int columnIndex) {
@@ -739,6 +751,7 @@ public final class SQLiteRawStatement implements Closeable {
* @return The value of the column as an long.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteException if a native error occurs.
*/
public long getColumnLong(int columnIndex) {
@@ -763,6 +776,7 @@ public final class SQLiteRawStatement implements Closeable {
* @return The value of the column as a string.
* @throws IllegalStateException if the statement is closed or this is a foreign thread.
* @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range.
+ * @throws SQLiteMisuseException if the row has no data. See {@link #getColumnType()}.
* @throws SQLiteException if a native error occurs.
*/
@NonNull
diff --git a/core/jni/android_database_SQLiteRawStatement.cpp b/core/jni/android_database_SQLiteRawStatement.cpp
index 85a6bdf95928..32c2ef73a5b1 100644
--- a/core/jni/android_database_SQLiteRawStatement.cpp
+++ b/core/jni/android_database_SQLiteRawStatement.cpp
@@ -70,12 +70,32 @@ static void throwInvalidParameter(JNIEnv *env, jlong stmtPtr, jint index) {
}
}
+// If the last operation failed, throw an exception and return true. Otherwise return false.
+static bool throwIfError(JNIEnv *env, jlong stmtPtr) {
+ switch (sqlite3_errcode(db(stmtPtr))) {
+ case SQLITE_OK:
+ case SQLITE_DONE:
+ case SQLITE_ROW: return false;
+ }
+ throw_sqlite3_exception(env, db(stmtPtr), nullptr);
+ return true;
+}
-// This throws a SQLiteBindOrColumnIndexOutOfRangeException if the column index is out
-// of bounds. It returns true if an exception was thrown.
+// This throws a SQLiteBindOrColumnIndexOutOfRangeException if the column index is out of
+// bounds. It throws SQLiteMisuseException if the statement's column count is zero; that
+// generally occurs because the client has forgotten to call step() or the client has stepped
+// past the end of the query. The function returns true if an exception was thrown.
static bool throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
- if (col < 0 || col >= sqlite3_data_count(stmt(stmtPtr))) {
- int count = sqlite3_data_count(stmt(stmtPtr));
+ int count = sqlite3_data_count(stmt(stmtPtr));
+ if (throwIfError(env, stmtPtr)) {
+ return true;
+ } else if (count == 0) {
+ // A count of zero indicates a misuse: the statement has never been step()'ed.
+ const char* message = "row has no data";
+ const char* errmsg = sqlite3_errstr(SQLITE_MISUSE);
+ throw_sqlite3_exception(env, SQLITE_MISUSE, errmsg, message);
+ return true;
+ } else if (col < 0 || col >= count) {
std::string message = android::base::StringPrintf(
"column index %d out of bounds [0,%d]", col, count - 1);
char const * errmsg = sqlite3_errstr(SQLITE_RANGE);
@@ -86,17 +106,6 @@ static bool throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
}
}
-// If the last operation failed, throw an exception and return true. Otherwise return false.
-static bool throwIfError(JNIEnv *env, jlong stmtPtr) {
- switch (sqlite3_errcode(db(stmtPtr))) {
- case SQLITE_OK:
- case SQLITE_DONE:
- case SQLITE_ROW: return false;
- }
- throw_sqlite3_exception(env, db(stmtPtr), nullptr);
- return true;
-}
-
static jint bindParameterCount(JNIEnv* env, jclass, jlong stmtPtr) {
return sqlite3_bind_parameter_count(stmt(stmtPtr));
}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 6dad3b7b2ac4..13b12fcf300a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -1010,6 +1010,7 @@ public class SQLiteRawStatementTest {
mDatabase.beginTransaction();
try {
mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
+ mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (2, 20)");
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
@@ -1017,13 +1018,35 @@ public class SQLiteRawStatementTest {
mDatabase.beginTransactionReadOnly();
try (SQLiteRawStatement s = mDatabase.createRawStatement("SELECT * from t1")) {
- s.step();
- s.getColumnText(5); // out-of-range column
+ assertTrue(s.step());
+ s.getColumnText(5); // out-of-range column: the range is [0,2).
fail("JNI exception not thrown");
} catch (SQLiteBindOrColumnIndexOutOfRangeException e) {
// Passing case.
} finally {
mDatabase.endTransaction();
}
+
+ mDatabase.beginTransactionReadOnly();
+ try (SQLiteRawStatement s = mDatabase.createRawStatement("SELECT * from t1")) {
+ // Do not step the statement. The column count will be zero.
+ s.getColumnText(5); // out-of-range column: never stepped.
+ fail("JNI exception not thrown");
+ } catch (SQLiteMisuseException e) {
+ // Passing case.
+ } finally {
+ mDatabase.endTransaction();
+ }
+
+ mDatabase.beginTransactionReadOnly();
+ try (SQLiteRawStatement s = mDatabase.createRawStatement("SELECT * from t1")) {
+ // Do not step the statement. The column count will be zero.
+ s.getColumnText(0); // out-of-range column: never stepped.
+ fail("JNI exception not thrown");
+ } catch (SQLiteMisuseException e) {
+ // Passing case.
+ } finally {
+ mDatabase.endTransaction();
+ }
}
}