calcite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jh...@apache.org
Subject [06/11] incubator-calcite git commit: [CALCITE-645] Implement AvaticaSqlException to pass server-side exception information to clients (Josh Elser)
Date Fri, 23 Oct 2015 02:38:55 GMT
[CALCITE-645] Implement AvaticaSqlException to pass server-side exception information to clients (Josh Elser)

Created ErrorResponse to serve as the message to pass between client and server.
Attempted to introduce information necessary to fill out SQLException. Some refactoring
of the server Handlers was included to unify exception handling.


Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/5be93fb4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/5be93fb4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/5be93fb4

Branch: refs/heads/master
Commit: 5be93fb40e7b41fdeeef57e622118427a90cc037
Parents: a63639b
Author: Josh Elser <elserj@apache.org>
Authored: Tue Oct 6 18:15:32 2015 -0400
Committer: Julian Hyde <jhyde@apache.org>
Committed: Thu Oct 22 18:28:33 2015 -0700

----------------------------------------------------------------------
 .../apache/calcite/avatica/jdbc/JdbcMeta.java   |   4 +-
 .../calcite/avatica/server/AvaticaHandler.java  |  35 +-
 .../avatica/server/AvaticaProtobufHandler.java  |  20 +-
 .../calcite/avatica/remote/RemoteMetaTest.java  |  41 +-
 .../avatica/AvaticaClientRuntimeException.java  |  86 +++
 .../apache/calcite/avatica/AvaticaSeverity.java |  90 +++
 .../calcite/avatica/AvaticaSqlException.java    |  59 ++
 .../calcite/avatica/AvaticaStatement.java       |   8 +-
 .../java/org/apache/calcite/avatica/Helper.java |  11 +
 .../java/org/apache/calcite/avatica/Meta.java   |   1 +
 .../apache/calcite/avatica/proto/Common.java    | 121 ++-
 .../apache/calcite/avatica/proto/Responses.java | 735 +++++++++++++++++--
 .../calcite/avatica/remote/AbstractHandler.java | 142 ++++
 .../avatica/remote/AvaticaRuntimeException.java | 102 +++
 .../apache/calcite/avatica/remote/Driver.java   |  14 +-
 .../apache/calcite/avatica/remote/Handler.java  |  28 +-
 .../calcite/avatica/remote/JsonHandler.java     |  28 +-
 .../calcite/avatica/remote/JsonService.java     |  11 +-
 .../calcite/avatica/remote/LocalService.java    |  57 +-
 .../calcite/avatica/remote/MockJsonService.java |  38 +-
 .../avatica/remote/MockProtobufService.java     |  42 +-
 .../calcite/avatica/remote/ProtobufHandler.java |  37 +-
 .../calcite/avatica/remote/ProtobufService.java |   3 +-
 .../avatica/remote/RemoteProtobufService.java   |  30 +-
 .../calcite/avatica/remote/RemoteService.java   |  17 +-
 .../apache/calcite/avatica/remote/Service.java  | 170 +++--
 avatica/src/main/protobuf/common.proto          |   9 +
 avatica/src/main/protobuf/responses.proto       |   7 +-
 .../avatica/remote/AbstractHandlerTest.java     | 165 +++++
 .../avatica/remote/ErrorResponseTest.java       |  63 ++
 .../avatica/remote/ProtobufHandlerTest.java     |   5 +-
 .../remote/ProtobufTranslationImplTest.java     |   9 +-
 .../test/AvaticaClientRuntimeExceptionTest.java |  50 ++
 .../avatica/test/AvaticaSeverityTest.java       |  39 +
 .../avatica/test/AvaticaSqlExceptionTest.java   |  49 ++
 .../calcite/test/ExceptionMessageTest.java      |   4 +-
 36 files changed, 2023 insertions(+), 307 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java b/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
index 4054939..3f00dcb 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
@@ -533,8 +533,8 @@ public class JdbcMeta implements Meta {
     }
   }
 
-  @Override
-  public void openConnection(ConnectionHandle ch, Map<String, String> info) {
+  @Override public void openConnection(ConnectionHandle ch,
+      Map<String, String> info) {
     Properties fullInfo = new Properties();
     fullInfo.putAll(this.info);
     if (info != null) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
index acb7fcf..f7c4800 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaHandler.java
@@ -17,17 +17,13 @@
 package org.apache.calcite.avatica.server;
 
 import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.remote.Handler.HandlerResponse;
 import org.apache.calcite.avatica.remote.JsonHandler;
 import org.apache.calcite.avatica.remote.Service;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Throwables;
-import com.google.common.collect.Iterables;
-
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 
@@ -68,36 +64,17 @@ public class AvaticaHandler extends AbstractHandler {
       if (LOG.isTraceEnabled()) {
         LOG.trace("request: " + jsonRequest);
       }
-      String jsonResponse;
-      try {
-        jsonResponse = jsonHandler.apply(jsonRequest);
-      } catch (Throwable t) {
-        LOG.error("Error handling request: " + jsonRequest, t);
-        response.setStatus(500);
-        jsonResponse = createErrorResponse(t);
-      }
+
+      final HandlerResponse<String> jsonResponse = jsonHandler.apply(jsonRequest);
       if (LOG.isTraceEnabled()) {
         LOG.trace("response: " + jsonResponse);
       }
       baseRequest.setHandled(true);
-      response.getWriter().println(jsonResponse);
+      // Set the status code and write out the response.
+      response.setStatus(jsonResponse.getStatusCode());
+      response.getWriter().println(jsonResponse.getResponse());
     }
   }
-
-  private String createErrorResponse(Throwable t) throws IOException {
-    return jsonHandler.encode(new Service.ErrorResponse(getErrorMessage(t)));
-  }
-
-  public static String getErrorMessage(Throwable t) {
-    return Joiner.on(" -> ").join(
-        Iterables.transform(Throwables.getCausalChain(t), new Function<Throwable, String>() {
-          @Override
-          public String apply(Throwable input) {
-            return input.getMessage() == null
-                ? "(null exception message)" : input.getMessage();
-          }
-        }));
-  }
 }
 
 // End AvaticaHandler.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
index 9432824..9dc6df0 100644
--- a/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
+++ b/avatica-server/src/main/java/org/apache/calcite/avatica/server/AvaticaProtobufHandler.java
@@ -17,6 +17,7 @@
 package org.apache.calcite.avatica.server;
 
 import org.apache.calcite.avatica.AvaticaUtils;
+import org.apache.calcite.avatica.remote.Handler.HandlerResponse;
 import org.apache.calcite.avatica.remote.ProtobufHandler;
 import org.apache.calcite.avatica.remote.ProtobufTranslation;
 import org.apache.calcite.avatica.remote.ProtobufTranslationImpl;
@@ -59,26 +60,13 @@ public class AvaticaProtobufHandler extends AbstractHandler {
         requestBytes = AvaticaUtils.readFullyToBytes(inputStream);
       }
 
-      byte[] responseBytes;
-      try {
-        responseBytes = pbHandler.apply(requestBytes);
-      } catch (Throwable t) {
-        LOG.error("Error handling request", t);
-        response.setStatus(500);
-        responseBytes = createErrorResponse(t);
-      }
+      HandlerResponse<byte[]> handlerResponse = pbHandler.apply(requestBytes);
 
       baseRequest.setHandled(true);
-      response.getOutputStream().write(responseBytes);
+      response.setStatus(handlerResponse.getStatusCode());
+      response.getOutputStream().write(handlerResponse.getResponse());
     }
   }
-
-  private byte[] createErrorResponse(Throwable t) throws IOException {
-    Service.ErrorResponse errorResponse = new Service.ErrorResponse(
-        AvaticaHandler.getErrorMessage(t));
-    return protobufTranslation.serializeResponse(errorResponse);
-  }
-
 }
 
 // End AvaticaProtobufHandler.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
----------------------------------------------------------------------
diff --git a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
index df5983a..5e14427 100644
--- a/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
+++ b/avatica-server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
@@ -17,11 +17,13 @@
 package org.apache.calcite.avatica.remote;
 
 import org.apache.calcite.avatica.AvaticaConnection;
+import org.apache.calcite.avatica.AvaticaSqlException;
 import org.apache.calcite.avatica.AvaticaStatement;
 import org.apache.calcite.avatica.ConnectionPropertiesImpl;
 import org.apache.calcite.avatica.ConnectionSpec;
 import org.apache.calcite.avatica.Meta;
 import org.apache.calcite.avatica.jdbc.JdbcMeta;
+import org.apache.calcite.avatica.remote.Service.ErrorResponse;
 import org.apache.calcite.avatica.server.AvaticaHandler;
 import org.apache.calcite.avatica.server.AvaticaProtobufHandler;
 import org.apache.calcite.avatica.server.HttpServer;
@@ -305,8 +307,8 @@ public class RemoteMetaTest {
       DriverManager.getConnection(url, "john", "doe");
       fail("expected exception");
     } catch (RuntimeException e) {
-      assertEquals("Remote driver error:"
-          + " java.sql.SQLInvalidAuthorizationSpecException: invalid authorization specification"
+      assertEquals("Remote driver error: "
+          + "java.sql.SQLInvalidAuthorizationSpecException: invalid authorization specification"
           + " - not found: john"
           + " -> invalid authorization specification - not found: john"
           + " -> invalid authorization specification - not found: john",
@@ -330,9 +332,9 @@ public class RemoteMetaTest {
       stmt2.executeQuery("select * from buffer");
       fail("expected exception");
     } catch (Exception e) {
-      assertEquals("Remote driver error: java.sql.SQLSyntaxErrorException: user lacks privilege"
-          + " or object not found: BUFFER -> user lacks privilege or object not found: BUFFER"
-          + " -> user lacks privilege or object not found: BUFFER", e.getCause().getMessage());
+      assertEquals("Error -1 (00000) : Error while executing SQL \"select * from buffer\": "
+          + "Remote driver error: user lacks privilege or object not found: BUFFER",
+          e.getMessage());
     }
   }
 
@@ -347,8 +349,33 @@ public class RemoteMetaTest {
       conn.createStatement();
       fail("expected exception");
     } catch (RuntimeException e) {
-      assertThat(e.getMessage(), containsString("Remote driver error:"
-          + " Connection not found: invalid id, closed, or expired"));
+      assertThat(e.getMessage(),
+          containsString("Connection not found: invalid id, closed, or expired"));
+    }
+  }
+
+  @Test public void testExceptionPropagation() throws Exception {
+    final String sql = "SELECT * from EMP LIMIT FOOBARBAZ";
+    ConnectionSpec.getDatabaseLock().lock();
+    try (final AvaticaConnection conn = (AvaticaConnection) DriverManager.getConnection(url);
+        final Statement stmt = conn.createStatement()) {
+      try {
+        // invalid SQL
+        stmt.execute(sql);
+        fail("Expected an AvaticaSqlException");
+      } catch (AvaticaSqlException e) {
+        assertEquals(ErrorResponse.UNKNOWN_ERROR_CODE, e.getErrorCode());
+        assertEquals(ErrorResponse.UNKNOWN_SQL_STATE, e.getSQLState());
+        assertTrue("Message should contain original SQL, was '" + e.getMessage() + "'",
+            e.getMessage().contains(sql));
+        assertEquals(1, e.getStackTraces().size());
+        final String stacktrace = e.getStackTraces().get(0);
+        final String substring = "unexpected token: FOOBARBAZ";
+        assertTrue("Message should contain '" + substring + "', was '" + e.getMessage() + ",",
+            stacktrace.contains(substring));
+      }
+    } finally {
+      ConnectionSpec.getDatabaseLock().unlock();
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java
new file mode 100644
index 0000000..6859763
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaClientRuntimeException.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica;
+
+import org.apache.calcite.avatica.remote.AvaticaRuntimeException;
+import org.apache.calcite.avatica.remote.Service.ErrorResponse;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The client-side representation of {@link AvaticaRuntimeException}. This exception is not intended
+ * for consumption by clients, {@link AvaticaSqlException} serves that purpose. This exception only
+ * exists to pass the original error attributes to a higher level of execution without modifying
+ * existing exception-handling logic.
+ */
+public class AvaticaClientRuntimeException extends RuntimeException {
+
+  private static final long serialVersionUID = 1L;
+
+  private final int errorCode;
+  private final String sqlState;
+  private final AvaticaSeverity severity;
+  private final List<String> serverExceptions;
+
+  public AvaticaClientRuntimeException(String errorMessage, int errorCode, String sqlState,
+      AvaticaSeverity severity, List<String> serverExceptions) {
+    super(errorMessage);
+    this.errorCode = errorCode;
+    this.sqlState = sqlState;
+    this.severity = severity;
+    this.serverExceptions = serverExceptions;
+  }
+
+  public AvaticaClientRuntimeException(String message, Throwable cause) {
+    super(message, cause);
+    errorCode = ErrorResponse.UNKNOWN_ERROR_CODE;
+    sqlState = ErrorResponse.UNKNOWN_SQL_STATE;
+    severity = AvaticaSeverity.UNKNOWN;
+    serverExceptions = Collections.singletonList("");
+  }
+
+  public int getErrorCode() {
+    return errorCode;
+  }
+
+  public String getSqlState() {
+    return sqlState;
+  }
+
+  public AvaticaSeverity getSeverity() {
+    return severity;
+  }
+
+  public List<String> getServerExceptions() {
+    return serverExceptions;
+  }
+
+  @Override public String toString() {
+    StringBuilder sb = new StringBuilder(64);
+    sb.append(getClass().getSimpleName()).append(": ")
+    .append(getMessage()).append(". Error ").append(getErrorCode())
+    .append(" (").append(sqlState).append(") ").append(getSeverity()).append("\n\n");
+    for (String serverException : getServerExceptions()) {
+      sb.append(serverException).append("\n");
+    }
+    return sb.toString();
+  }
+
+}
+
+// End AvaticaClientRuntimeException.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSeverity.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSeverity.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSeverity.java
new file mode 100644
index 0000000..675c8af
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSeverity.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica;
+
+import org.apache.calcite.avatica.proto.Common;
+
+import java.util.Objects;
+
+/**
+ * An enumeration that denotes the severity of a given unexpected state.
+ */
+public enum AvaticaSeverity {
+  /**
+   * The severity of the outcome of some unexpected state is unknown.
+   */
+  UNKNOWN(0),
+
+  /**
+   * The system has been left in an unrecoverable state as a result of an operation.
+   */
+  FATAL(1),
+
+  /**
+   * The result of an action resulted in an error which the current operation cannot recover
+   * from. Clients can attempt to execute the operation again.
+   */
+  ERROR(2),
+
+  /**
+   * The operation completed successfully but a message was generated to warn the client about
+   * some unexpected state or action.
+   */
+  WARNING(3);
+
+  private final int value;
+
+  AvaticaSeverity(int value) {
+    this.value = value;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  public Common.Severity toProto() {
+    switch (this) {
+    case UNKNOWN:
+      return Common.Severity.UNKNOWN_SEVERITY;
+    case FATAL:
+      return Common.Severity.FATAL_SEVERITY;
+    case ERROR:
+      return Common.Severity.ERROR_SEVERITY;
+    case WARNING:
+      return Common.Severity.WARNING_SEVERITY;
+    default:
+      throw new RuntimeException("Unhandled Severity level: " + this);
+    }
+  }
+
+  public static AvaticaSeverity fromProto(Common.Severity proto) {
+    switch (Objects.requireNonNull(proto)) {
+    case UNKNOWN_SEVERITY:
+      return AvaticaSeverity.UNKNOWN;
+    case FATAL_SEVERITY:
+      return AvaticaSeverity.FATAL;
+    case ERROR_SEVERITY:
+      return AvaticaSeverity.ERROR;
+    case WARNING_SEVERITY:
+      return AvaticaSeverity.WARNING;
+    default:
+      throw new RuntimeException("Unhandled protobuf Severity level: " + proto);
+    }
+  }
+}
+
+// End AvaticaSeverity.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
new file mode 100644
index 0000000..9782581
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A client-facing {@link SQLException} which encapsulates errors from the remote Avatica server.
+ */
+public class AvaticaSqlException extends SQLException {
+
+  private static final long serialVersionUID = 1L;
+
+  private final String errorMessage;
+  private final List<String> stackTraces;
+
+  /**
+   * Construct the Exception with information from the server.
+   *
+   * @param errorMessage A human-readable error message.
+   * @param errorCode An integer corresponding to a known error.
+   * @param stackTraces Server-side stacktrace.
+   */
+  public AvaticaSqlException(String errorMessage, String sqlState, int errorCode,
+      List<String> stackTraces) {
+    super("Error " + errorCode + " (" + sqlState + ") : " + errorMessage, sqlState, errorCode);
+    this.errorMessage = errorMessage;
+    this.stackTraces = Objects.requireNonNull(stackTraces);
+  }
+
+  public String getErrorMessage() {
+    return errorMessage;
+  }
+
+  /**
+   * @return The stacktraces for exceptions thrown on the Avatica server.
+   */
+  public List<String> getStackTraces() {
+    return stackTraces;
+  }
+}
+
+// End AvaticaSqlException.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
index 1b338c3..6bcf8eb 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
@@ -133,8 +133,8 @@ public abstract class AvaticaStatement
       Meta.ExecuteResult x =
           connection.prepareAndExecuteInternal(this, sql, maxRowCount1);
     } catch (RuntimeException e) {
-      throw connection.helper.createException(
-          "error while executing SQL \"" + sql + "\": " + e.getMessage(), e);
+      throw connection.helper.createException("Error while executing SQL \"" + sql + "\": "
+          + e.getMessage(), e);
     }
   }
 
@@ -158,8 +158,8 @@ public abstract class AvaticaStatement
       }
       return openResultSet;
     } catch (RuntimeException e) {
-      throw connection.helper.createException(
-        "error while executing SQL \"" + sql + "\": " + e.getMessage(), e);
+      throw connection.helper.createException("Error while executing SQL \"" + sql + "\": "
+          + e.getMessage(), e);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/Helper.java b/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
index 184aef5..54be099 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/Helper.java
@@ -38,6 +38,17 @@ public class Helper {
   }
 
   public SQLException createException(String message, Exception e) {
+    return createException(message, null, e);
+  }
+
+  public SQLException createException(String message, String sql, Exception e) {
+    if (e instanceof AvaticaClientRuntimeException) {
+      // The AvaticaClientRuntimeException contains extra information about what/why
+      // the exception was thrown that we can pass back to the user.
+      AvaticaClientRuntimeException rte = (AvaticaClientRuntimeException) e;
+      return new AvaticaSqlException(message, rte.getSqlState(), rte.getErrorCode(),
+          rte.getServerExceptions());
+    }
     return new SQLException(message, e);
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/Meta.java b/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
index a16ffa1..c5c8030 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/Meta.java
@@ -42,6 +42,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
 
 /**
  * Command handler for getting various metadata. Should be implemented by each

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/proto/Common.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/proto/Common.java b/avatica/src/main/java/org/apache/calcite/avatica/proto/Common.java
index c4ed1e7..f66f493 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/proto/Common.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/proto/Common.java
@@ -527,6 +527,121 @@ package org.apache.calcite.avatica.proto;
     // @@protoc_insertion_point(enum_scope:Rep)
   }
 
+  /**
+   * Protobuf enum {@code Severity}
+   *
+   * <pre>
+   * The severity of some unexpected outcome to an operation.
+   * Protobuf enum values must be unique across all other enums
+   * </pre>
+   */
+  public enum Severity
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>UNKNOWN_SEVERITY = 0;</code>
+     */
+    UNKNOWN_SEVERITY(0, 0),
+    /**
+     * <code>FATAL_SEVERITY = 1;</code>
+     */
+    FATAL_SEVERITY(1, 1),
+    /**
+     * <code>ERROR_SEVERITY = 2;</code>
+     */
+    ERROR_SEVERITY(2, 2),
+    /**
+     * <code>WARNING_SEVERITY = 3;</code>
+     */
+    WARNING_SEVERITY(3, 3),
+    UNRECOGNIZED(-1, -1),
+    ;
+
+    /**
+     * <code>UNKNOWN_SEVERITY = 0;</code>
+     */
+    public static final int UNKNOWN_SEVERITY_VALUE = 0;
+    /**
+     * <code>FATAL_SEVERITY = 1;</code>
+     */
+    public static final int FATAL_SEVERITY_VALUE = 1;
+    /**
+     * <code>ERROR_SEVERITY = 2;</code>
+     */
+    public static final int ERROR_SEVERITY_VALUE = 2;
+    /**
+     * <code>WARNING_SEVERITY = 3;</code>
+     */
+    public static final int WARNING_SEVERITY_VALUE = 3;
+
+
+    public final int getNumber() {
+      if (index == -1) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    public static Severity valueOf(int value) {
+      switch (value) {
+        case 0: return UNKNOWN_SEVERITY;
+        case 1: return FATAL_SEVERITY;
+        case 2: return ERROR_SEVERITY;
+        case 3: return WARNING_SEVERITY;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<Severity>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static com.google.protobuf.Internal.EnumLiteMap<Severity>
+        internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<Severity>() {
+            public Severity findValueByNumber(int number) {
+              return Severity.valueOf(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(index);
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return org.apache.calcite.avatica.proto.Common.getDescriptor().getEnumTypes().get(2);
+    }
+
+    private static final Severity[] VALUES = values();
+
+    public static Severity valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int index;
+    private final int value;
+
+    private Severity(int index, int value) {
+      this.index = index;
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:Severity)
+  }
+
   public interface ConnectionPropertiesOrBuilder extends
       // @@protoc_insertion_point(interface_extends:ConnectionProperties)
       com.google.protobuf.MessageOrBuilder {
@@ -12550,8 +12665,10 @@ package org.apache.calcite.avatica.proto;
       "ME\020\020\022\026\n\022JAVA_SQL_TIMESTAMP\020\021\022\021\n\rJAVA_SQL" +
       "_DATE\020\022\022\022\n\016JAVA_UTIL_DATE\020\023\022\017\n\013BYTE_STRI" +
       "NG\020\024\022\n\n\006STRING\020\025\022\n\n\006NUMBER\020\026\022\n\n\006OBJECT\020\027" +
-      "\022\010\n\004NULL\020\030B\"\n org.apache.calcite.avatica" +
-      ".protob\006proto3"
+      "\022\010\n\004NULL\020\030*^\n\010Severity\022\024\n\020UNKNOWN_SEVERI" +
+      "TY\020\000\022\022\n\016FATAL_SEVERITY\020\001\022\022\n\016ERROR_SEVERI",
+      "TY\020\002\022\024\n\020WARNING_SEVERITY\020\003B\"\n org.apache" +
+      ".calcite.avatica.protob\006proto3"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
         new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/proto/Responses.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/proto/Responses.java b/avatica/src/main/java/org/apache/calcite/avatica/proto/Responses.java
index 2fceb8a..e41078a 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/proto/Responses.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/proto/Responses.java
@@ -5944,17 +5944,100 @@ package org.apache.calcite.avatica.proto;
       com.google.protobuf.MessageOrBuilder {
 
     /**
-     * <code>optional string message = 1;</code>
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
+     */
+    com.google.protobuf.ProtocolStringList
+        getExceptionsList();
+    /**
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
+     */
+    int getExceptionsCount();
+    /**
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
+     */
+    java.lang.String getExceptions(int index);
+    /**
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
+     */
+    com.google.protobuf.ByteString
+        getExceptionsBytes(int index);
+
+    /**
+     * <code>optional string error_message = 2;</code>
+     *
+     * <pre>
+     * human readable description
+     * </pre>
      */
-    java.lang.String getMessage();
+    java.lang.String getErrorMessage();
     /**
-     * <code>optional string message = 1;</code>
+     * <code>optional string error_message = 2;</code>
+     *
+     * <pre>
+     * human readable description
+     * </pre>
+     */
+    com.google.protobuf.ByteString
+        getErrorMessageBytes();
+
+    /**
+     * <code>optional .Severity severity = 3;</code>
+     */
+    int getSeverityValue();
+    /**
+     * <code>optional .Severity severity = 3;</code>
+     */
+    org.apache.calcite.avatica.proto.Common.Severity getSeverity();
+
+    /**
+     * <code>optional uint32 error_code = 4;</code>
+     *
+     * <pre>
+     * numeric identifier for error
+     * </pre>
+     */
+    int getErrorCode();
+
+    /**
+     * <code>optional string sql_state = 5;</code>
+     *
+     * <pre>
+     * five-character standard-defined value
+     * </pre>
+     */
+    java.lang.String getSqlState();
+    /**
+     * <code>optional string sql_state = 5;</code>
+     *
+     * <pre>
+     * five-character standard-defined value
+     * </pre>
      */
     com.google.protobuf.ByteString
-        getMessageBytes();
+        getSqlStateBytes();
   }
   /**
    * Protobuf type {@code ErrorResponse}
+   *
+   * <pre>
+   * Send contextual information about some error over the wire from the server.
+   * </pre>
    */
   public  static final class ErrorResponse extends
       com.google.protobuf.GeneratedMessage implements
@@ -5965,7 +6048,11 @@ package org.apache.calcite.avatica.proto;
       super(builder);
     }
     private ErrorResponse() {
-      message_ = "";
+      exceptions_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      errorMessage_ = "";
+      severity_ = 0;
+      errorCode_ = 0;
+      sqlState_ = "";
     }
 
     @java.lang.Override
@@ -5995,8 +6082,34 @@ package org.apache.calcite.avatica.proto;
             }
             case 10: {
               com.google.protobuf.ByteString bs = input.readBytes();
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+                exceptions_ = new com.google.protobuf.LazyStringArrayList();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              exceptions_.add(bs);
+              break;
+            }
+            case 18: {
+              com.google.protobuf.ByteString bs = input.readBytes();
 
-              message_ = bs;
+              errorMessage_ = bs;
+              break;
+            }
+            case 24: {
+              int rawValue = input.readEnum();
+
+              severity_ = rawValue;
+              break;
+            }
+            case 32: {
+
+              errorCode_ = input.readUInt32();
+              break;
+            }
+            case 42: {
+              com.google.protobuf.ByteString bs = input.readBytes();
+
+              sqlState_ = bs;
               break;
             }
           }
@@ -6007,6 +6120,9 @@ package org.apache.calcite.avatica.proto;
         throw new com.google.protobuf.InvalidProtocolBufferException(
             e.getMessage()).setUnfinishedMessage(this);
       } finally {
+        if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+          exceptions_ = exceptions_.getUnmodifiableView();
+        }
         makeExtensionsImmutable();
       }
     }
@@ -6037,13 +6153,63 @@ package org.apache.calcite.avatica.proto;
       return PARSER;
     }
 
-    public static final int MESSAGE_FIELD_NUMBER = 1;
-    private java.lang.Object message_;
+    private int bitField0_;
+    public static final int EXCEPTIONS_FIELD_NUMBER = 1;
+    private com.google.protobuf.LazyStringList exceptions_;
     /**
-     * <code>optional string message = 1;</code>
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
      */
-    public java.lang.String getMessage() {
-      java.lang.Object ref = message_;
+    public com.google.protobuf.ProtocolStringList
+        getExceptionsList() {
+      return exceptions_;
+    }
+    /**
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
+     */
+    public int getExceptionsCount() {
+      return exceptions_.size();
+    }
+    /**
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
+     */
+    public java.lang.String getExceptions(int index) {
+      return exceptions_.get(index);
+    }
+    /**
+     * <code>repeated string exceptions = 1;</code>
+     *
+     * <pre>
+     * exception stacktraces, many for linked exceptions.
+     * </pre>
+     */
+    public com.google.protobuf.ByteString
+        getExceptionsBytes(int index) {
+      return exceptions_.getByteString(index);
+    }
+
+    public static final int ERROR_MESSAGE_FIELD_NUMBER = 2;
+    private java.lang.Object errorMessage_;
+    /**
+     * <code>optional string error_message = 2;</code>
+     *
+     * <pre>
+     * human readable description
+     * </pre>
+     */
+    public java.lang.String getErrorMessage() {
+      java.lang.Object ref = errorMessage_;
       if (ref instanceof java.lang.String) {
         return (java.lang.String) ref;
       } else {
@@ -6051,22 +6217,99 @@ package org.apache.calcite.avatica.proto;
             (com.google.protobuf.ByteString) ref;
         java.lang.String s = bs.toStringUtf8();
         if (bs.isValidUtf8()) {
-          message_ = s;
+          errorMessage_ = s;
         }
         return s;
       }
     }
     /**
-     * <code>optional string message = 1;</code>
+     * <code>optional string error_message = 2;</code>
+     *
+     * <pre>
+     * human readable description
+     * </pre>
      */
     public com.google.protobuf.ByteString
-        getMessageBytes() {
-      java.lang.Object ref = message_;
+        getErrorMessageBytes() {
+      java.lang.Object ref = errorMessage_;
       if (ref instanceof java.lang.String) {
         com.google.protobuf.ByteString b = 
             com.google.protobuf.ByteString.copyFromUtf8(
                 (java.lang.String) ref);
-        message_ = b;
+        errorMessage_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int SEVERITY_FIELD_NUMBER = 3;
+    private int severity_;
+    /**
+     * <code>optional .Severity severity = 3;</code>
+     */
+    public int getSeverityValue() {
+      return severity_;
+    }
+    /**
+     * <code>optional .Severity severity = 3;</code>
+     */
+    public org.apache.calcite.avatica.proto.Common.Severity getSeverity() {
+      org.apache.calcite.avatica.proto.Common.Severity result = org.apache.calcite.avatica.proto.Common.Severity.valueOf(severity_);
+      return result == null ? org.apache.calcite.avatica.proto.Common.Severity.UNRECOGNIZED : result;
+    }
+
+    public static final int ERROR_CODE_FIELD_NUMBER = 4;
+    private int errorCode_;
+    /**
+     * <code>optional uint32 error_code = 4;</code>
+     *
+     * <pre>
+     * numeric identifier for error
+     * </pre>
+     */
+    public int getErrorCode() {
+      return errorCode_;
+    }
+
+    public static final int SQL_STATE_FIELD_NUMBER = 5;
+    private java.lang.Object sqlState_;
+    /**
+     * <code>optional string sql_state = 5;</code>
+     *
+     * <pre>
+     * five-character standard-defined value
+     * </pre>
+     */
+    public java.lang.String getSqlState() {
+      java.lang.Object ref = sqlState_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          sqlState_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <code>optional string sql_state = 5;</code>
+     *
+     * <pre>
+     * five-character standard-defined value
+     * </pre>
+     */
+    public com.google.protobuf.ByteString
+        getSqlStateBytes() {
+      java.lang.Object ref = sqlState_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        sqlState_ = b;
         return b;
       } else {
         return (com.google.protobuf.ByteString) ref;
@@ -6086,8 +6329,20 @@ package org.apache.calcite.avatica.proto;
     public void writeTo(com.google.protobuf.CodedOutputStream output)
                         throws java.io.IOException {
       getSerializedSize();
-      if (!getMessageBytes().isEmpty()) {
-        output.writeBytes(1, getMessageBytes());
+      for (int i = 0; i < exceptions_.size(); i++) {
+        output.writeBytes(1, exceptions_.getByteString(i));
+      }
+      if (!getErrorMessageBytes().isEmpty()) {
+        output.writeBytes(2, getErrorMessageBytes());
+      }
+      if (severity_ != org.apache.calcite.avatica.proto.Common.Severity.UNKNOWN_SEVERITY.getNumber()) {
+        output.writeEnum(3, severity_);
+      }
+      if (errorCode_ != 0) {
+        output.writeUInt32(4, errorCode_);
+      }
+      if (!getSqlStateBytes().isEmpty()) {
+        output.writeBytes(5, getSqlStateBytes());
       }
     }
 
@@ -6097,9 +6352,30 @@ package org.apache.calcite.avatica.proto;
       if (size != -1) return size;
 
       size = 0;
-      if (!getMessageBytes().isEmpty()) {
+      {
+        int dataSize = 0;
+        for (int i = 0; i < exceptions_.size(); i++) {
+          dataSize += com.google.protobuf.CodedOutputStream
+            .computeBytesSizeNoTag(exceptions_.getByteString(i));
+        }
+        size += dataSize;
+        size += 1 * getExceptionsList().size();
+      }
+      if (!getErrorMessageBytes().isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(2, getErrorMessageBytes());
+      }
+      if (severity_ != org.apache.calcite.avatica.proto.Common.Severity.UNKNOWN_SEVERITY.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(3, severity_);
+      }
+      if (errorCode_ != 0) {
         size += com.google.protobuf.CodedOutputStream
-          .computeBytesSize(1, getMessageBytes());
+          .computeUInt32Size(4, errorCode_);
+      }
+      if (!getSqlStateBytes().isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(5, getSqlStateBytes());
       }
       memoizedSerializedSize = size;
       return size;
@@ -6174,6 +6450,10 @@ package org.apache.calcite.avatica.proto;
     }
     /**
      * Protobuf type {@code ErrorResponse}
+     *
+     * <pre>
+     * Send contextual information about some error over the wire from the server.
+     * </pre>
      */
     public static final class Builder extends
         com.google.protobuf.GeneratedMessage.Builder<Builder> implements
@@ -6207,7 +6487,15 @@ package org.apache.calcite.avatica.proto;
       }
       public Builder clear() {
         super.clear();
-        message_ = "";
+        exceptions_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        errorMessage_ = "";
+
+        severity_ = 0;
+
+        errorCode_ = 0;
+
+        sqlState_ = "";
 
         return this;
       }
@@ -6231,7 +6519,18 @@ package org.apache.calcite.avatica.proto;
 
       public org.apache.calcite.avatica.proto.Responses.ErrorResponse buildPartial() {
         org.apache.calcite.avatica.proto.Responses.ErrorResponse result = new org.apache.calcite.avatica.proto.Responses.ErrorResponse(this);
-        result.message_ = message_;
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          exceptions_ = exceptions_.getUnmodifiableView();
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.exceptions_ = exceptions_;
+        result.errorMessage_ = errorMessage_;
+        result.severity_ = severity_;
+        result.errorCode_ = errorCode_;
+        result.sqlState_ = sqlState_;
+        result.bitField0_ = to_bitField0_;
         onBuilt();
         return result;
       }
@@ -6247,8 +6546,28 @@ package org.apache.calcite.avatica.proto;
 
       public Builder mergeFrom(org.apache.calcite.avatica.proto.Responses.ErrorResponse other) {
         if (other == org.apache.calcite.avatica.proto.Responses.ErrorResponse.getDefaultInstance()) return this;
-        if (!other.getMessage().isEmpty()) {
-          message_ = other.message_;
+        if (!other.exceptions_.isEmpty()) {
+          if (exceptions_.isEmpty()) {
+            exceptions_ = other.exceptions_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureExceptionsIsMutable();
+            exceptions_.addAll(other.exceptions_);
+          }
+          onChanged();
+        }
+        if (!other.getErrorMessage().isEmpty()) {
+          errorMessage_ = other.errorMessage_;
+          onChanged();
+        }
+        if (other.severity_ != 0) {
+          setSeverityValue(other.getSeverityValue());
+        }
+        if (other.getErrorCode() != 0) {
+          setErrorCode(other.getErrorCode());
+        }
+        if (!other.getSqlState().isEmpty()) {
+          sqlState_ = other.sqlState_;
           onChanged();
         }
         onChanged();
@@ -6276,19 +6595,325 @@ package org.apache.calcite.avatica.proto;
         }
         return this;
       }
+      private int bitField0_;
+
+      private com.google.protobuf.LazyStringList exceptions_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      private void ensureExceptionsIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          exceptions_ = new com.google.protobuf.LazyStringArrayList(exceptions_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public com.google.protobuf.ProtocolStringList
+          getExceptionsList() {
+        return exceptions_.getUnmodifiableView();
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public int getExceptionsCount() {
+        return exceptions_.size();
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public java.lang.String getExceptions(int index) {
+        return exceptions_.get(index);
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public com.google.protobuf.ByteString
+          getExceptionsBytes(int index) {
+        return exceptions_.getByteString(index);
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public Builder setExceptions(
+          int index, java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureExceptionsIsMutable();
+        exceptions_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public Builder addExceptions(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureExceptionsIsMutable();
+        exceptions_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public Builder addAllExceptions(
+          java.lang.Iterable<java.lang.String> values) {
+        ensureExceptionsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, exceptions_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public Builder clearExceptions() {
+        exceptions_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated string exceptions = 1;</code>
+       *
+       * <pre>
+       * exception stacktraces, many for linked exceptions.
+       * </pre>
+       */
+      public Builder addExceptionsBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureExceptionsIsMutable();
+        exceptions_.add(value);
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object errorMessage_ = "";
+      /**
+       * <code>optional string error_message = 2;</code>
+       *
+       * <pre>
+       * human readable description
+       * </pre>
+       */
+      public java.lang.String getErrorMessage() {
+        java.lang.Object ref = errorMessage_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (bs.isValidUtf8()) {
+            errorMessage_ = s;
+          }
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>optional string error_message = 2;</code>
+       *
+       * <pre>
+       * human readable description
+       * </pre>
+       */
+      public com.google.protobuf.ByteString
+          getErrorMessageBytes() {
+        java.lang.Object ref = errorMessage_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          errorMessage_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>optional string error_message = 2;</code>
+       *
+       * <pre>
+       * human readable description
+       * </pre>
+       */
+      public Builder setErrorMessage(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        errorMessage_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>optional string error_message = 2;</code>
+       *
+       * <pre>
+       * human readable description
+       * </pre>
+       */
+      public Builder clearErrorMessage() {
+        
+        errorMessage_ = getDefaultInstance().getErrorMessage();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>optional string error_message = 2;</code>
+       *
+       * <pre>
+       * human readable description
+       * </pre>
+       */
+      public Builder setErrorMessageBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        errorMessage_ = value;
+        onChanged();
+        return this;
+      }
 
-      private java.lang.Object message_ = "";
+      private int severity_ = 0;
+      /**
+       * <code>optional .Severity severity = 3;</code>
+       */
+      public int getSeverityValue() {
+        return severity_;
+      }
       /**
-       * <code>optional string message = 1;</code>
+       * <code>optional .Severity severity = 3;</code>
        */
-      public java.lang.String getMessage() {
-        java.lang.Object ref = message_;
+      public Builder setSeverityValue(int value) {
+        severity_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>optional .Severity severity = 3;</code>
+       */
+      public org.apache.calcite.avatica.proto.Common.Severity getSeverity() {
+        org.apache.calcite.avatica.proto.Common.Severity result = org.apache.calcite.avatica.proto.Common.Severity.valueOf(severity_);
+        return result == null ? org.apache.calcite.avatica.proto.Common.Severity.UNRECOGNIZED : result;
+      }
+      /**
+       * <code>optional .Severity severity = 3;</code>
+       */
+      public Builder setSeverity(org.apache.calcite.avatica.proto.Common.Severity value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        severity_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>optional .Severity severity = 3;</code>
+       */
+      public Builder clearSeverity() {
+        
+        severity_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int errorCode_ ;
+      /**
+       * <code>optional uint32 error_code = 4;</code>
+       *
+       * <pre>
+       * numeric identifier for error
+       * </pre>
+       */
+      public int getErrorCode() {
+        return errorCode_;
+      }
+      /**
+       * <code>optional uint32 error_code = 4;</code>
+       *
+       * <pre>
+       * numeric identifier for error
+       * </pre>
+       */
+      public Builder setErrorCode(int value) {
+        
+        errorCode_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>optional uint32 error_code = 4;</code>
+       *
+       * <pre>
+       * numeric identifier for error
+       * </pre>
+       */
+      public Builder clearErrorCode() {
+        
+        errorCode_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object sqlState_ = "";
+      /**
+       * <code>optional string sql_state = 5;</code>
+       *
+       * <pre>
+       * five-character standard-defined value
+       * </pre>
+       */
+      public java.lang.String getSqlState() {
+        java.lang.Object ref = sqlState_;
         if (!(ref instanceof java.lang.String)) {
           com.google.protobuf.ByteString bs =
               (com.google.protobuf.ByteString) ref;
           java.lang.String s = bs.toStringUtf8();
           if (bs.isValidUtf8()) {
-            message_ = s;
+            sqlState_ = s;
           }
           return s;
         } else {
@@ -6296,53 +6921,69 @@ package org.apache.calcite.avatica.proto;
         }
       }
       /**
-       * <code>optional string message = 1;</code>
+       * <code>optional string sql_state = 5;</code>
+       *
+       * <pre>
+       * five-character standard-defined value
+       * </pre>
        */
       public com.google.protobuf.ByteString
-          getMessageBytes() {
-        java.lang.Object ref = message_;
+          getSqlStateBytes() {
+        java.lang.Object ref = sqlState_;
         if (ref instanceof String) {
           com.google.protobuf.ByteString b = 
               com.google.protobuf.ByteString.copyFromUtf8(
                   (java.lang.String) ref);
-          message_ = b;
+          sqlState_ = b;
           return b;
         } else {
           return (com.google.protobuf.ByteString) ref;
         }
       }
       /**
-       * <code>optional string message = 1;</code>
+       * <code>optional string sql_state = 5;</code>
+       *
+       * <pre>
+       * five-character standard-defined value
+       * </pre>
        */
-      public Builder setMessage(
+      public Builder setSqlState(
           java.lang.String value) {
         if (value == null) {
     throw new NullPointerException();
   }
   
-        message_ = value;
+        sqlState_ = value;
         onChanged();
         return this;
       }
       /**
-       * <code>optional string message = 1;</code>
+       * <code>optional string sql_state = 5;</code>
+       *
+       * <pre>
+       * five-character standard-defined value
+       * </pre>
        */
-      public Builder clearMessage() {
+      public Builder clearSqlState() {
         
-        message_ = getDefaultInstance().getMessage();
+        sqlState_ = getDefaultInstance().getSqlState();
         onChanged();
         return this;
       }
       /**
-       * <code>optional string message = 1;</code>
+       * <code>optional string sql_state = 5;</code>
+       *
+       * <pre>
+       * five-character standard-defined value
+       * </pre>
        */
-      public Builder setMessageBytes(
+      public Builder setSqlStateBytes(
           com.google.protobuf.ByteString value) {
         if (value == null) {
     throw new NullPointerException();
   }
   
-        message_ = value;
+        sqlState_ = value;
         onChanged();
         return this;
       }
@@ -6462,9 +7103,11 @@ package org.apache.calcite.avatica.proto;
       "nt\022\036\n\003key\030\001 \001(\0132\021.DatabaseProperty\022\032\n\005va" +
       "lue\030\002 \001(\0132\013.TypedValue\"C\n\030DatabaseProper" +
       "tyResponse\022\'\n\005props\030\001 \003(\0132\030.DatabaseProp" +
-      "ertyElement\" \n\rErrorResponse\022\017\n\007message\030" +
-      "\001 \001(\tB\"\n org.apache.calcite.avatica.prot",
-      "ob\006proto3"
+      "ertyElement\"~\n\rErrorResponse\022\022\n\nexceptio" +
+      "ns\030\001 \003(\t\022\025\n\rerror_message\030\002 \001(\t\022\033\n\010sever",
+      "ity\030\003 \001(\0162\t.Severity\022\022\n\nerror_code\030\004 \001(\r" +
+      "\022\021\n\tsql_state\030\005 \001(\tB\"\n org.apache.calcit" +
+      "e.avatica.protob\006proto3"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
         new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
@@ -6550,7 +7193,7 @@ package org.apache.calcite.avatica.proto;
     internal_static_ErrorResponse_fieldAccessorTable = new
       com.google.protobuf.GeneratedMessage.FieldAccessorTable(
         internal_static_ErrorResponse_descriptor,
-        new java.lang.String[] { "Message", });
+        new java.lang.String[] { "Exceptions", "ErrorMessage", "Severity", "ErrorCode", "SqlState", });
     org.apache.calcite.avatica.proto.Common.getDescriptor();
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java
new file mode 100644
index 0000000..9d3bf66
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.remote;
+
+import org.apache.calcite.avatica.AvaticaSeverity;
+import org.apache.calcite.avatica.remote.Service.ErrorResponse;
+import org.apache.calcite.avatica.remote.Service.Request;
+import org.apache.calcite.avatica.remote.Service.Response;
+
+import java.io.IOException;
+
+/**
+ * Abstract base class for {@link Handler}s to extend to inherit functionality common across
+ * serialization strategies.
+ *
+ * @param <T> The format Requests/Responses are serialized as.
+ */
+public abstract class AbstractHandler<T> implements Handler<T> {
+  private static final String NULL_EXCEPTION_MESSAGE = "(null exception message)";
+  protected final Service service;
+
+  public AbstractHandler(Service service) {
+    this.service = service;
+  }
+
+  abstract Request decode(T serializedRequest) throws IOException;
+
+  /**
+   * Serialize the given {@link Response} per the concrete {@link Handler} implementation.
+   *
+   * @param response The {@link Response} to serialize.
+   * @return A serialized representation of the {@link Response}.
+   * @throws IOException
+   */
+  abstract T encode(Response response) throws IOException;
+
+  /**
+   * Unwrap Avatica-specific context about a given exception.
+   *
+   * @param e A caught exception throw by Avatica implementation.
+   * @return An {@link ErrorResponse}.
+   */
+  ErrorResponse unwrapException(Exception e) {
+    // By default, we know nothing extra.
+    int errorCode = ErrorResponse.UNKNOWN_ERROR_CODE;
+    String sqlState = ErrorResponse.UNKNOWN_SQL_STATE;
+    AvaticaSeverity severity = AvaticaSeverity.UNKNOWN;
+    String errorMsg = null;
+
+    // Extract the contextual information if we have it. We may not.
+    if (e instanceof AvaticaRuntimeException) {
+      AvaticaRuntimeException rte = (AvaticaRuntimeException) e;
+      errorCode = rte.getErrorCode();
+      sqlState = rte.getSqlState();
+      severity = rte.getSeverity();
+      errorMsg = rte.getErrorMessage();
+    } else {
+      // Try to construct a meaningful error message when the server impl doesn't provide one.
+      errorMsg = getCausalChain(e);
+    }
+
+    return new ErrorResponse(e, errorMsg, errorCode, sqlState, severity);
+  }
+
+  /**
+   * Compute a response for the given request, handling errors generated by that computation.
+   *
+   * @param serializedRequest The caller's request.
+   * @return A {@link Response} with additional context about that response.
+   */
+  public HandlerResponse<T> apply(T serializedRequest) {
+    final Service.Request request;
+    try {
+      request = decode(serializedRequest);
+    } catch (IOException e) {
+      // TODO provide a canned ErrorResponse.
+      throw new RuntimeException(e);
+    }
+
+    try {
+      final Service.Response response = request.accept(service);
+      return new HandlerResponse<>(encode(response), HTTP_OK);
+    } catch (Exception e) {
+      ErrorResponse errorResp = unwrapException(e);
+
+      try {
+        return new HandlerResponse<>(encode(errorResp), HTTP_INTERNAL_SERVER_ERROR);
+      } catch (IOException e1) {
+        // TODO provide a canned ErrorResponse
+
+        // If we can't serialize error message to JSON, can't give a meaningful error to caller.
+        // Just try to not unnecessarily create more exceptions.
+        if (e instanceof RuntimeException) {
+          throw (RuntimeException) e;
+        }
+
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  /**
+   * Constructs a message for the summary of an Exception.
+   *
+   * @param e The Exception to summarize.
+   * @return A summary message for the Exception.
+   */
+  private String getCausalChain(Exception e) {
+    StringBuilder sb = new StringBuilder(16);
+    Throwable curr = e;
+    // Could use Guava, but that would increase dependency set unnecessarily.
+    while (null != curr) {
+      if (sb.length() > 0) {
+        sb.append(" -> ");
+      }
+      String message = curr.getMessage();
+      sb.append(null == message ? NULL_EXCEPTION_MESSAGE : message);
+      curr = curr.getCause();
+    }
+    if (sb.length() == 0) {
+      // Catch the case where we have no error message.
+      return "Unknown error message";
+    }
+    return sb.toString();
+  }
+}
+
+// End AbstractHandler.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/AvaticaRuntimeException.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/AvaticaRuntimeException.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/AvaticaRuntimeException.java
new file mode 100644
index 0000000..2f9a1cd
--- /dev/null
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/AvaticaRuntimeException.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.remote;
+
+import org.apache.calcite.avatica.AvaticaSeverity;
+import org.apache.calcite.avatica.remote.Service.ErrorResponse;
+
+import java.util.Objects;
+
+/**
+ * A {@link RuntimeException} thrown by Avatica with additional contextual information about
+ * what happened to cause the Exception.
+ */
+public class AvaticaRuntimeException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
+  private final String errorMessage;
+  private final int errorCode;
+  private final String sqlState;
+  private final AvaticaSeverity severity;
+
+  /**
+   * Constructs an {@code AvaticaRuntimeException} with no additional information.
+   *
+   * <p>It is strongly preferred that the caller invoke
+   * {@link #AvaticaRuntimeException(String, int, String, AvaticaSeverity)}
+   * with proper contextual information.
+   */
+  public AvaticaRuntimeException() {
+    this("No additional context on exception", ErrorResponse.UNKNOWN_ERROR_CODE,
+        ErrorResponse.UNKNOWN_SQL_STATE, AvaticaSeverity.UNKNOWN);
+  }
+
+  /**
+   * Constructs an {@code AvaticaRuntimeException} with the given
+   * contextual information surrounding the error.
+   *
+   * @param errorMessage A human-readable explanation about what happened
+   * @param errorCode Numeric identifier for error
+   * @param sqlState 5-character identifier for error
+   * @param severity Severity
+   */
+  public AvaticaRuntimeException(String errorMessage, int errorCode, String sqlState,
+      AvaticaSeverity severity) {
+    this.errorMessage = Objects.requireNonNull(errorMessage);
+    this.errorCode = errorCode;
+    this.sqlState = Objects.requireNonNull(sqlState);
+    this.severity = Objects.requireNonNull(severity);
+  }
+
+  /**
+   * Returns a human-readable error message.
+   */
+  public String getErrorMessage() {
+    return errorMessage;
+  }
+
+  /**
+   * Returns a numeric code for this error.
+   */
+  public int getErrorCode() {
+    return errorCode;
+  }
+
+  /**
+   * Returns the five-character identifier for this error.
+   */
+  public String getSqlState() {
+    return sqlState;
+  }
+
+  /**
+   * Returns the severity at which this exception is thrown.
+   */
+  public AvaticaSeverity getSeverity() {
+    return severity;
+  }
+
+  @Override public String toString() {
+    StringBuilder sb = new StringBuilder(64);
+    return sb.append("AvaticaRuntimeException: [")
+        .append("Messsage: '").append(errorMessage).append("', ")
+        .append("Error code: '").append(errorCode).append("', ")
+        .append("SQL State: '").append(sqlState).append("', ")
+        .append("Severity: '").append(severity).append("']").toString();
+  }
+}
+
+// End AvaticaRuntimeException.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/Driver.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/Driver.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/Driver.java
index ea5561d..0ab90b1 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/Driver.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/Driver.java
@@ -81,15 +81,11 @@ public class Driver extends UnregisteredDriver {
 
   @Override public Meta createMeta(AvaticaConnection connection) {
     final ConnectionConfig config = connection.config();
-    final Service service = createService(config);
+    final Service service = createService(connection, config);
     return new RemoteMeta(connection, service);
   }
 
-  private Service createService(ConnectionConfig config) {
-    // Exploit that none of the factory implementations currently rely
-    // on connection being there.
-    AvaticaConnection connection = null;
-
+  private Service createService(AvaticaConnection connection, ConnectionConfig config) {
     final Service.Factory metaFactory = config.factory();
     final Service service;
     if (metaFactory != null) {
@@ -120,8 +116,8 @@ public class Driver extends UnregisteredDriver {
     return service;
   }
 
-  @Override
-  public Connection connect(String url, Properties info) throws SQLException {
+  @Override public Connection connect(String url, Properties info)
+      throws SQLException {
     AvaticaConnection conn = (AvaticaConnection) super.connect(url, info);
     if (conn == null) {
       // It's not an url for our driver
@@ -130,7 +126,7 @@ public class Driver extends UnregisteredDriver {
 
     // Create the corresponding remote connection
     ConnectionConfig config = conn.config();
-    Service service = createService(config);
+    Service service = createService(conn, config);
 
     Map<String, String> infoAsString = new HashMap<>();
     for (Map.Entry<Object, Object> entry : info.entrySet()) {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/Handler.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/Handler.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/Handler.java
index a3f564d..f0e8a93 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/Handler.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/Handler.java
@@ -16,13 +16,39 @@
  */
 package org.apache.calcite.avatica.remote;
 
+import java.util.Objects;
+
 /**
  * API for text request-response calls to an Avatica server.
  *
  * @param <T> The type this handler accepts and returns
  */
 public interface Handler<T> {
-  T apply(T request);
+  int HTTP_OK = 200;
+  int HTTP_INTERNAL_SERVER_ERROR = 500;
+
+  /**
+   * Struct that encapsulates the context of the result of a request to Avatica.
+   */
+  public class HandlerResponse<T> {
+    private final T response;
+    private final int statusCode;
+
+    public HandlerResponse(T response, int statusCode) {
+      this.response = Objects.requireNonNull(response);
+      this.statusCode = statusCode;
+    }
+
+    public T getResponse() {
+      return response;
+    }
+
+    public int getStatusCode() {
+      return statusCode;
+    }
+  }
+
+  HandlerResponse<T> apply(T request);
 }
 
 // End Handler.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonHandler.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonHandler.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonHandler.java
index be1f5a7..5afc760 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonHandler.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonHandler.java
@@ -16,6 +16,9 @@
  */
 package org.apache.calcite.avatica.remote;
 
+import org.apache.calcite.avatica.remote.Service.Request;
+import org.apache.calcite.avatica.remote.Service.Response;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import java.io.IOException;
@@ -28,27 +31,20 @@ import java.io.StringWriter;
  *
  * @see org.apache.calcite.avatica.remote.JsonService
  */
-public class JsonHandler implements Handler<String> {
-  private final Service service;
+public class JsonHandler extends AbstractHandler<String> {
 
   protected static final ObjectMapper MAPPER = JsonService.MAPPER;
 
   public JsonHandler(Service service) {
-    this.service = service;
+    super(service);
   }
 
-  public String apply(String jsonRequest) {
-    try {
-      Service.Request request = decode(jsonRequest, Service.Request.class);
-      final Service.Response response = request.accept(service);
-      return encode(response);
-    } catch (IOException e) {
-      throw handle(e);
-    }
+  public HandlerResponse<String> apply(String jsonRequest) {
+    return super.apply(jsonRequest);
   }
 
-  private <T> T decode(String request, Class<T> valueType) throws IOException {
-    return MAPPER.readValue(request, valueType);
+  @Override Request decode(String request) throws IOException {
+    return MAPPER.readValue(request, Service.Request.class);
   }
 
   /**
@@ -57,15 +53,11 @@ public class JsonHandler implements Handler<String> {
    * @param response The object to serialize.
    * @return A JSON string.
    */
-  public <T> String encode(T response) throws IOException {
+  @Override String encode(Response response) throws IOException {
     final StringWriter w = new StringWriter();
     MAPPER.writeValue(w, response);
     return w.toString();
   }
-
-  protected RuntimeException handle(IOException e) {
-    return new RuntimeException(e);
-  }
 }
 
 // End JsonHandler.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
index 1bdb4a6..86d69f1 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/JsonService.java
@@ -44,9 +44,16 @@ public abstract class JsonService extends AbstractService {
   public abstract String apply(String request);
 
   //@VisibleForTesting
-  protected static <T> T decode(String response, Class<T> valueType)
+  protected static <T> T decode(String response, Class<T> expectedType)
       throws IOException {
-    return MAPPER.readValue(response, valueType);
+    Response resp = MAPPER.readValue(response, Response.class);
+    if (resp instanceof ErrorResponse) {
+      throw ((ErrorResponse) resp).toException();
+    } else if (!expectedType.isAssignableFrom(resp.getClass())) {
+      throw new ClassCastException("Cannot cast " + resp.getClass() + " into " + expectedType);
+    }
+
+    return expectedType.cast(resp);
   }
 
   //@VisibleForTesting

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
index 34bdf97..1a5a554 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/LocalService.java
@@ -110,21 +110,25 @@ public class LocalService implements Service {
   }
 
   public ResultSetResponse apply(CatalogsRequest request) {
-    final Meta.MetaResultSet resultSet =
-        meta.getCatalogs(new Meta.ConnectionHandle(request.connectionId));
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
+    final Meta.MetaResultSet resultSet = meta.getCatalogs(ch);
     return toResponse(resultSet);
   }
 
   public ResultSetResponse apply(SchemasRequest request) {
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
     final Meta.MetaResultSet resultSet =
-        meta.getSchemas(new Meta.ConnectionHandle(request.connectionId),
-            request.catalog, Meta.Pat.of(request.schemaPattern));
+        meta.getSchemas(ch, request.catalog, Meta.Pat.of(request.schemaPattern));
     return toResponse(resultSet);
   }
 
   public ResultSetResponse apply(TablesRequest request) {
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
     final Meta.MetaResultSet resultSet =
-        meta.getTables(new Meta.ConnectionHandle(request.connectionId),
+        meta.getTables(ch,
             request.catalog,
             Meta.Pat.of(request.schemaPattern),
             Meta.Pat.of(request.tableNamePattern),
@@ -133,21 +137,24 @@ public class LocalService implements Service {
   }
 
   public ResultSetResponse apply(TableTypesRequest request) {
-    final Meta.MetaResultSet resultSet = meta.getTableTypes(
-        new Meta.ConnectionHandle(request.connectionId));
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
+    final Meta.MetaResultSet resultSet = meta.getTableTypes(ch);
     return toResponse(resultSet);
   }
 
   public ResultSetResponse apply(TypeInfoRequest request) {
-    final Meta.MetaResultSet resultSet = meta.getTypeInfo(
-        new Meta.ConnectionHandle(request.connectionId));
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
+    final Meta.MetaResultSet resultSet = meta.getTypeInfo(ch);
     return toResponse(resultSet);
   }
 
   public ResultSetResponse apply(ColumnsRequest request) {
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
     final Meta.MetaResultSet resultSet =
-        meta.getColumns(
-            new Meta.ConnectionHandle(request.connectionId),
+        meta.getColumns(ch,
             request.catalog,
             Meta.Pat.of(request.schemaPattern),
             Meta.Pat.of(request.tableNamePattern),
@@ -212,37 +219,45 @@ public class LocalService implements Service {
   }
 
   public CreateStatementResponse apply(CreateStatementRequest request) {
-    final Meta.StatementHandle h =
-        meta.createStatement(new Meta.ConnectionHandle(request.connectionId));
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
+    final Meta.StatementHandle h = meta.createStatement(ch);
     return new CreateStatementResponse(h.connectionId, h.id);
   }
 
   public CloseStatementResponse apply(CloseStatementRequest request) {
-    meta.closeStatement(
-        new Meta.StatementHandle(request.connectionId, request.statementId,
-            null));
+    final Meta.StatementHandle h = new Meta.StatementHandle(
+        request.connectionId, request.statementId, null);
+    meta.closeStatement(h);
     return new CloseStatementResponse();
   }
 
   public OpenConnectionResponse apply(OpenConnectionRequest request) {
-    meta.openConnection(new Meta.ConnectionHandle(request.connectionId), request.info);
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
+    meta.openConnection(ch, request.info);
     return new OpenConnectionResponse();
   }
 
   public CloseConnectionResponse apply(CloseConnectionRequest request) {
-    meta.closeConnection(new Meta.ConnectionHandle(request.connectionId));
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
+    meta.closeConnection(ch);
     return new CloseConnectionResponse();
   }
 
   public ConnectionSyncResponse apply(ConnectionSyncRequest request) {
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
     final Meta.ConnectionProperties connProps =
-        meta.connectionSync(new Meta.ConnectionHandle(request.connectionId), request.connProps);
+        meta.connectionSync(ch, request.connProps);
     return new ConnectionSyncResponse(connProps);
   }
 
   public DatabasePropertyResponse apply(DatabasePropertyRequest request) {
-    return new DatabasePropertyResponse(meta.getDatabaseProperties(
-        new Meta.ConnectionHandle(request.connectionId)));
+    final Meta.ConnectionHandle ch =
+        new Meta.ConnectionHandle(request.connectionId);
+    return new DatabasePropertyResponse(meta.getDatabaseProperties(ch));
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
----------------------------------------------------------------------
diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
index 6d87122..2c2c7b2 100644
--- a/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
+++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/MockJsonService.java
@@ -18,9 +18,6 @@ package org.apache.calcite.avatica.remote;
 
 import org.apache.calcite.avatica.AvaticaConnection;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -33,62 +30,43 @@ import java.util.Map;
  */
 public class MockJsonService extends JsonService {
   private final Map<String, String> map;
-  private final ObjectMapper mapper = new ObjectMapper();
 
   public MockJsonService(Map<String, String> map) {
     this.map = map;
   }
 
   @Override public String apply(String request) {
-    String response = map.get(canonicalizeConnectionId(request));
+    String response = map.get(request);
     if (response == null) {
       throw new RuntimeException("No response for " + request);
     }
     return response;
   }
 
-  /**
-   * The connection id is always different, therefore when present in the request,
-   * set it to 0.
-   */
-  private String canonicalizeConnectionId(String request) {
-    ObjectNode jsonRequest;
-    try {
-      jsonRequest = (ObjectNode) mapper.readTree(request);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-    if (jsonRequest.get("connectionId") != null) {
-      jsonRequest.put("connectionId", "0");
-      return jsonRequest.toString();
-    } else {
-      return request;
-    }
-  }
-
   /** Factory that creates a {@code MockJsonService}. */
   public static class Factory implements Service.Factory {
     public Service create(AvaticaConnection connection) {
+      final String connectionId = connection.handle.id;
       final Map<String, String> map1 = new HashMap<>();
       try {
         map1.put(
-            "{\"request\":\"openConnection\",\"connectionId\":\"0\",\"info\":{}}",
+            "{\"request\":\"openConnection\",\"connectionId\":\"" + connectionId + "\",\"info\":{}}",
             "{\"response\":\"openConnection\"}");
         map1.put(
-            "{\"request\":\"closeConnection\",\"connectionId\":\"0\"}",
+            "{\"request\":\"closeConnection\",\"connectionId\":\"" + connectionId + "\"}",
             "{\"response\":\"closeConnection\"}");
         map1.put(
             "{\"request\":\"getSchemas\",\"catalog\":null,\"schemaPattern\":{\"s\":null}}",
             "{\"response\":\"resultSet\", updateCount: -1, firstFrame: {offset: 0, done: true, rows: []}}");
         map1.put(
-            JsonService.encode(new SchemasRequest("0", null, null)),
+            JsonService.encode(new SchemasRequest(connectionId, null, null)),
             "{\"response\":\"resultSet\", updateCount: -1, firstFrame: {offset: 0, done: true, rows: []}}");
         map1.put(
             JsonService.encode(
-                new TablesRequest("0", null, null, null, Arrays.<String>asList())),
+                new TablesRequest(connectionId, null, null, null, Arrays.<String>asList())),
             "{\"response\":\"resultSet\", updateCount: -1, firstFrame: {offset: 0, done: true, rows: []}}");
         map1.put(
-            "{\"request\":\"createStatement\",\"connectionId\":0}",
+            "{\"request\":\"createStatement\",\"connectionId\":\"" + connectionId + "\"}",
             "{\"response\":\"createStatement\",\"id\":0}");
         map1.put(
             "{\"request\":\"prepareAndExecute\",\"statementId\":0,"
@@ -111,7 +89,7 @@ public class MockJsonService extends JsonService {
                 + " \"cursorFactory\": {\"style\": \"ARRAY\"}\n"
                 + "}}");
         map1.put(
-            "{\"request\":\"getColumns\",\"catalog\":null,\"schemaPattern\":null,"
+            "{\"request\":\"getColumns\",\"connectionId\":\"" + connectionId + "\",\"catalog\":null,\"schemaPattern\":null,"
                 + "\"tableNamePattern\":\"my_table\",\"columnNamePattern\":null}",
             "{\"response\":\"resultSet\",\"connectionId\":\"00000000-0000-0000-0000-000000000000\",\"statementId\":-1,\"ownStatement\":true,"
                 + "\"signature\":{\"columns\":["


Mime
View raw message