SqliteInspector: Added error codes
Allows for differentiating between errors at a higher granularity.
Bug: 143216096
Test: ./gradlew :sqlite:sqlite-inspection:cC
Change-Id: Ia5877a83b87f4a1d1b3eb146ea76befc8f43e1ac
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/BasicTest.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/BasicTest.kt
index 5bde00f..6695cea 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/BasicTest.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/BasicTest.kt
@@ -17,6 +17,7 @@
package androidx.sqlite.inspection.test
import androidx.sqlite.inspection.SqliteInspectorProtocol
+import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_UNRECOGNISED_COMMAND_VALUE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -55,6 +56,9 @@
assertThat(response.hasErrorOccurred()).isEqualTo(true)
assertThat(response.errorOccurred.content.message)
.contains("Unrecognised command type: ONEOF_NOT_SET")
+ assertThat(response.errorOccurred.content.errorCodeValue).isEqualTo(
+ ERROR_UNRECOGNISED_COMMAND_VALUE
+ )
}
}
}
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/GetSchemaTest.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/GetSchemaTest.kt
index f3f3305..65e475d 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/GetSchemaTest.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/GetSchemaTest.kt
@@ -17,6 +17,7 @@
package androidx.sqlite.inspection.test
import android.database.sqlite.SQLiteDatabase
+import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_NO_OPEN_DATABASE_WITH_REQUESTED_ID_VALUE
import androidx.sqlite.inspection.test.MessageFactory.createGetSchemaCommand
import androidx.sqlite.inspection.test.MessageFactory.createTrackDatabasesCommand
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -168,6 +169,9 @@
"Unable to perform an operation on database (id=$databaseId).")
assertThat(error.message).contains("The database may have already been closed.")
assertThat(error.recoverability.isRecoverable).isEqualTo(true)
+ assertThat(error.errorCodeValue).isEqualTo(
+ ERROR_NO_OPEN_DATABASE_WITH_REQUESTED_ID_VALUE
+ )
}
}
diff --git a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/QueryTest.kt b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/QueryTest.kt
index 31577be..b4e0595 100644
--- a/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/QueryTest.kt
+++ b/sqlite/sqlite-inspection/src/androidTest/java/androidx/sqlite/inspection/test/QueryTest.kt
@@ -19,6 +19,8 @@
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import androidx.sqlite.inspection.SqliteInspectorProtocol.CellValue
+import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_ISSUE_WITH_PROCESSING_QUERY_VALUE
+import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_NO_OPEN_DATABASE_WITH_REQUESTED_ID_VALUE
import androidx.sqlite.inspection.SqliteInspectorProtocol.QueryResponse
import androidx.sqlite.inspection.SqliteInspectorProtocol.Row
import androidx.sqlite.inspection.test.MessageFactory.createGetSchemaCommand
@@ -126,6 +128,9 @@
assertThat(error.message).contains("The database may have already been closed.")
assertThat(error.stackTrace).isEqualTo("")
assertThat(error.recoverability.isRecoverable).isEqualTo(true)
+ assertThat(error.errorCodeValue).isEqualTo(
+ ERROR_NO_OPEN_DATABASE_WITH_REQUESTED_ID_VALUE
+ )
}
}
@@ -145,6 +150,7 @@
assertThat(error.stackTrace).contains("SQLiteConnection.nativePrepareStatement")
assertThat(error.stackTrace).contains("SQLiteDatabase.rawQueryWithFactory")
assertThat(error.recoverability.isRecoverable).isEqualTo(true)
+ assertThat(error.errorCodeValue).isEqualTo(ERROR_ISSUE_WITH_PROCESSING_QUERY_VALUE)
}
@Test
@@ -163,6 +169,7 @@
assertThat(error.stackTrace).contains("SQLiteDirectCursorDriver.query")
assertThat(error.stackTrace).contains("SQLiteProgram.bind")
assertThat(error.recoverability.isRecoverable).isEqualTo(true)
+ assertThat(error.errorCodeValue).isEqualTo(ERROR_ISSUE_WITH_PROCESSING_QUERY_VALUE)
}
@Test
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseExtensions.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseExtensions.java
index d17df96..61c2c60 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseExtensions.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/DatabaseExtensions.java
@@ -60,12 +60,16 @@
database.acquireReference();
return true; // success
} catch (IllegalStateException e) {
- String message = e.getMessage();
- if (message != null
- && message.contains("attempt to re-open an already-closed object")) {
- return false; // too late to secure a reference
+ if (isAttemptAtUsingClosedDatabase(e)) {
+ return false;
}
- throw e; // unexpected exception
+ throw e;
}
}
+
+ static boolean isAttemptAtUsingClosedDatabase(IllegalStateException exception) {
+ String message = exception.getMessage();
+ return message != null
+ && message.contains("attempt to re-open an already-closed object");
+ }
}
diff --git a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
index 8893ee3..394a38e 100644
--- a/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
+++ b/sqlite/sqlite-inspection/src/main/java/androidx/sqlite/inspection/SqliteInspector.java
@@ -18,7 +18,14 @@
import static android.database.DatabaseUtils.getSqlStatementType;
+import static androidx.sqlite.inspection.DatabaseExtensions.isAttemptAtUsingClosedDatabase;
import static androidx.sqlite.inspection.SqliteInspectionExecutors.directExecutor;
+import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_DB_CLOSED_DURING_OPERATION;
+import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_ISSUE_WITH_PROCESSING_NEW_DATABASE_CONNECTION;
+import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_ISSUE_WITH_PROCESSING_QUERY;
+import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_NO_OPEN_DATABASE_WITH_REQUESTED_ID;
+import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_UNKNOWN;
+import static androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode.ERROR_UNRECOGNISED_COMMAND;
import android.annotation.SuppressLint;
import android.app.Application;
@@ -47,6 +54,7 @@
import androidx.sqlite.inspection.SqliteInspectorProtocol.DatabaseOpenedEvent;
import androidx.sqlite.inspection.SqliteInspectorProtocol.DatabasePossiblyChangedEvent;
import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent;
+import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorContent.ErrorCode;
import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorOccurredEvent;
import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorOccurredResponse;
import androidx.sqlite.inspection.SqliteInspectorProtocol.ErrorRecoverability;
@@ -208,8 +216,8 @@
createErrorOccurredResponse(
"Unrecognised command type: " + command.getOneOfCase().name(),
null,
- true
- ).toByteArray());
+ true,
+ ERROR_UNRECOGNISED_COMMAND).toByteArray());
}
} catch (Exception exception) {
callback.reply(
@@ -217,8 +225,8 @@
"Unhandled Exception while processing the command: "
+ exception.getMessage(),
stackTraceFromException(exception),
- null
- ).toByteArray()
+ null,
+ ERROR_UNKNOWN).toByteArray()
);
}
}
@@ -292,7 +300,8 @@
"Unhandled Exception while processing an onDatabaseAdded "
+ "event: "
+ exception.getMessage(),
- stackTraceFromException(exception), null)
+ stackTraceFromException(exception), null,
+ ERROR_ISSUE_WITH_PROCESSING_NEW_DATABASE_CONNECTION)
.toByteArray());
}
return database;
@@ -489,14 +498,25 @@
.toByteArray()
);
triggerInvalidation(command.getQuery());
- } catch (SQLiteException | IllegalArgumentException exception) {
- callback.reply(createErrorOccurredResponse(exception, true).toByteArray());
+ } catch (SQLiteException | IllegalArgumentException e) {
+ callback.reply(createErrorOccurredResponse(e, true,
+ ERROR_ISSUE_WITH_PROCESSING_QUERY).toByteArray());
+ } catch (IllegalStateException e) {
+ if (isAttemptAtUsingClosedDatabase(e)) {
+ callback.reply(createErrorOccurredResponse(e, true,
+ ERROR_DB_CLOSED_DURING_OPERATION).toByteArray());
+ } else {
+ callback.reply(createErrorOccurredResponse(e, null,
+ ERROR_UNKNOWN).toByteArray());
+ }
+ } catch (Exception e) {
+ callback.reply(createErrorOccurredResponse(e, null,
+ ERROR_UNKNOWN).toByteArray());
} finally {
if (cursor != null) {
cursor.close();
}
}
-
}
});
callback.addCancellationListener(directExecutor(), new Runnable() {
@@ -628,7 +648,8 @@
private void replyNoDatabaseWithId(CommandCallback callback, int databaseId) {
String message = String.format("Unable to perform an operation on database (id=%s)."
+ " The database may have already been closed.", databaseId);
- callback.reply(createErrorOccurredResponse(message, null, true).toByteArray());
+ callback.reply(createErrorOccurredResponse(message, null, true,
+ ERROR_NO_OPEN_DATABASE_WITH_REQUESTED_ID).toByteArray());
}
private @NonNull Response querySchema(SQLiteDatabase database) {
@@ -679,6 +700,17 @@
}
return Response.newBuilder().setGetSchema(schemaBuilder.build()).build();
+ } catch (IllegalStateException e) {
+ if (isAttemptAtUsingClosedDatabase(e)) {
+ return createErrorOccurredResponse(e, true,
+ ERROR_DB_CLOSED_DURING_OPERATION);
+ } else {
+ return createErrorOccurredResponse(e, null,
+ ERROR_UNKNOWN);
+ }
+ } catch (Exception e) {
+ return createErrorOccurredResponse(e, null,
+ ERROR_UNKNOWN);
} finally {
if (cursor != null) {
cursor.close();
@@ -698,19 +730,20 @@
}
private Event createErrorOccurredEvent(@Nullable String message, @Nullable String stackTrace,
- Boolean isRecoverable) {
+ Boolean isRecoverable, ErrorCode errorCode) {
return Event.newBuilder().setErrorOccurred(
ErrorOccurredEvent.newBuilder()
.setContent(
createErrorContentMessage(message,
stackTrace,
- isRecoverable))
+ isRecoverable,
+ errorCode))
.build())
.build();
}
private static ErrorContent createErrorContentMessage(@Nullable String message,
- @Nullable String stackTrace, Boolean isRecoverable) {
+ @Nullable String stackTrace, Boolean isRecoverable, ErrorCode errorCode) {
ErrorContent.Builder builder = ErrorContent.newBuilder();
if (message != null) {
builder.setMessage(message);
@@ -723,22 +756,23 @@
recoverability.setIsRecoverable(isRecoverable);
}
builder.setRecoverability(recoverability.build());
+ builder.setErrorCode(errorCode);
return builder.build();
}
private static Response createErrorOccurredResponse(@NonNull Exception exception,
- Boolean isRecoverable) {
+ Boolean isRecoverable, ErrorCode errorCode) {
return createErrorOccurredResponse(exception.getMessage(),
- stackTraceFromException(exception), isRecoverable);
+ stackTraceFromException(exception), isRecoverable, errorCode);
}
private static Response createErrorOccurredResponse(@Nullable String message,
- @Nullable String stackTrace, Boolean isRecoverable) {
+ @Nullable String stackTrace, Boolean isRecoverable, ErrorCode errorCode) {
return Response.newBuilder()
.setErrorOccurred(
ErrorOccurredResponse.newBuilder()
.setContent(createErrorContentMessage(message, stackTrace,
- isRecoverable)))
+ isRecoverable, errorCode)))
.build();
}
diff --git a/sqlite/sqlite-inspection/src/main/proto/live_sql_protocol.proto b/sqlite/sqlite-inspection/src/main/proto/live_sql_protocol.proto
index d0a93c6..6443157 100644
--- a/sqlite/sqlite-inspection/src/main/proto/live_sql_protocol.proto
+++ b/sqlite/sqlite-inspection/src/main/proto/live_sql_protocol.proto
@@ -158,6 +158,18 @@
string stack_trace = 2;
// Recoverability information
ErrorRecoverability recoverability = 3;
+ // Error code
+ enum ErrorCode {
+ NOT_SET = 0;
+ ERROR_UNKNOWN = 10;
+ ERROR_UNRECOGNISED_COMMAND = 20;
+ ERROR_DATABASE_VERSION_TOO_OLD = 30;
+ ERROR_ISSUE_WITH_PROCESSING_QUERY = 40;
+ ERROR_NO_OPEN_DATABASE_WITH_REQUESTED_ID = 50;
+ ERROR_ISSUE_WITH_PROCESSING_NEW_DATABASE_CONNECTION = 60;
+ ERROR_DB_CLOSED_DURING_OPERATION = 70;
+ }
+ ErrorCode error_code = 4;
}
// Recoverability of an error: