[go: nahoru, domu]

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: