http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/MockProtobufService.java ---------------------------------------------------------------------- diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/MockProtobufService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/MockProtobufService.java index fe357fd..8cca9d9 100644 --- a/avatica/src/main/java/org/apache/calcite/avatica/remote/MockProtobufService.java +++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/MockProtobufService.java @@ -34,23 +34,30 @@ import java.util.Map; */ public class MockProtobufService extends ProtobufService { - private static final Map<Request, Response> MAPPING; - static { + private final String connectionId; + private final Map<Request, Response> mapping; + + public MockProtobufService(String connectionId) { + this.connectionId = connectionId; + this.mapping = createMapping(); + } + + private Map<Request, Response> createMapping() { HashMap<Request, Response> mappings = new HashMap<>(); // Add in mappings mappings.put( - new OpenConnectionRequest("0", new HashMap<String, String>()), + new OpenConnectionRequest(connectionId, new HashMap<String, String>()), new OpenConnectionResponse()); // Get the schema, no.. schema..? mappings.put( - new SchemasRequest("0", null, null), + new SchemasRequest(connectionId, null, null), new ResultSetResponse(null, 1, true, null, Meta.Frame.EMPTY, -1)); // Get the tables, no tables exist - mappings.put(new TablesRequest("0", null, null, null, Collections.<String>emptyList()), + mappings.put(new TablesRequest(connectionId, null, null, null, Collections.<String>emptyList()), new ResultSetResponse(null, 150, true, null, Meta.Frame.EMPTY, -1)); // Create a statement, get back an id @@ -58,7 +65,7 @@ public class MockProtobufService extends ProtobufService { // Prepare and execute a query. Values and schema are returned mappings.put( - new PrepareAndExecuteRequest("0", 1, + new PrepareAndExecuteRequest(connectionId, 1, "select * from (\\n values (1, 'a'), (null, 'b'), (3, 'c')) as t (c1, c2)", -1), new ResultSetResponse("0", 1, true, Meta.Signature.create( @@ -72,7 +79,7 @@ public class MockProtobufService extends ProtobufService { // Prepare a query. Schema for results are returned, but no values mappings.put( - new PrepareRequest("0", + new PrepareRequest(connectionId, "select * from (\\n values(1, 'a'), (null, 'b'), (3, 'c')), as t (c1, c2)", -1), new ResultSetResponse("0", 1, true, Meta.Signature.create( @@ -84,7 +91,7 @@ public class MockProtobufService extends ProtobufService { null, -1)); mappings.put( - new ColumnsRequest(null, null, "my_table", null), + new ColumnsRequest(connectionId, null, null, "my_table", null), new ResultSetResponse("00000000-0000-0000-0000-000000000000", -1, true, Meta.Signature.create( Arrays.<ColumnMetaData>asList( @@ -94,7 +101,7 @@ public class MockProtobufService extends ProtobufService { Meta.Frame.create(0, true, Arrays.<Object>asList(new Object[] {new Object[]{"my_table", 10}})), -1)); - MAPPING = Collections.unmodifiableMap(mappings); + return Collections.unmodifiableMap(mappings); } @Override public Response _apply(Request request) { @@ -113,20 +120,7 @@ public class MockProtobufService extends ProtobufService { * @throws RuntimeException if no mapping is found for the request */ private Response dispatch(Request request) { - // Canonicalize connectionId's to 0 - if (request instanceof OpenConnectionRequest) { - OpenConnectionRequest req = (OpenConnectionRequest) request; - request = new OpenConnectionRequest("0", req.info); - } else if (request instanceof TablesRequest) { - TablesRequest req = (TablesRequest) request; - request = new TablesRequest("0", req.catalog, req.schemaPattern, - req.tableNamePattern, req.typeList); - } else if (request instanceof SchemasRequest) { - SchemasRequest req = (SchemasRequest) request; - request = new SchemasRequest("0", req.catalog, req.schemaPattern); - } - - Response response = MAPPING.get(request); + Response response = mapping.get(request); if (null == response) { throw new RuntimeException("Had no response mapping for " + request); @@ -140,7 +134,7 @@ public class MockProtobufService extends ProtobufService { */ public static class MockProtobufServiceFactory implements Service.Factory { @Override public Service create(AvaticaConnection connection) { - return new MockProtobufService(); + return new MockProtobufService(connection.handle.id); } } }
http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufHandler.java ---------------------------------------------------------------------- diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufHandler.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufHandler.java index 52f0a03..d77b52d 100644 --- a/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufHandler.java +++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufHandler.java @@ -18,8 +18,6 @@ package org.apache.calcite.avatica.remote; import org.apache.calcite.avatica.remote.Service.Response; -import com.google.protobuf.InvalidProtocolBufferException; - import java.io.IOException; /** @@ -27,36 +25,25 @@ import java.io.IOException; * by converting them to the POJO Request. Returns back the serialized protocol * buffer response. */ -public class ProtobufHandler implements Handler<byte[]> { +public class ProtobufHandler extends AbstractHandler<byte[]> { - private final Service service; private final ProtobufTranslation translation; public ProtobufHandler(Service service, ProtobufTranslation translation) { - this.service = service; + super(service); this.translation = translation; } - @Override public byte[] apply(byte[] requestBytes) { - // Transform the protocol buffer bytes into a POJO - // Encapsulate the task of transforming this since - // the bytes also contain the PB request class name. - Service.Request requestPojo; - try { - requestPojo = translation.parseRequest(requestBytes); - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - - // Get the response for the request - Response response = requestPojo.accept(service); - - try { - // Serialize it into bytes for the wire. - return translation.serializeResponse(response); - } catch (IOException e) { - throw new RuntimeException(e); - } + @Override public HandlerResponse<byte[]> apply(byte[] requestBytes) { + return super.apply(requestBytes); + } + + @Override Service.Request decode(byte[] serializedRequest) throws IOException { + return translation.parseRequest(serializedRequest); + } + + @Override byte[] encode(Response response) throws IOException { + return translation.serializeResponse(response); } } http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufService.java ---------------------------------------------------------------------- diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufService.java index e41e9f6..35cb35a 100644 --- a/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufService.java +++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/ProtobufService.java @@ -74,8 +74,7 @@ public abstract class ProtobufService extends AbstractService { return (CloseStatementResponse) _apply(request); } - @Override - public OpenConnectionResponse apply(OpenConnectionRequest request) { + @Override public OpenConnectionResponse apply(OpenConnectionRequest request) { return (OpenConnectionResponse) _apply(request); } http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteProtobufService.java ---------------------------------------------------------------------- diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteProtobufService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteProtobufService.java index 8cf0f0d..fa447de 100644 --- a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteProtobufService.java +++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteProtobufService.java @@ -38,6 +38,8 @@ public class RemoteProtobufService extends ProtobufService { } @Override public Response _apply(Request request) { + final InputStream inputStream; + try { final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); @@ -52,21 +54,29 @@ public class RemoteProtobufService extends ProtobufService { } final int responseCode = connection.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { - InputStream errorStream = connection.getErrorStream(); - if (errorStream != null) { - byte[] errorResponse = AvaticaUtils.readFullyToBytes(errorStream); - ErrorResponse response = (ErrorResponse) translation.parseResponse(errorResponse); - throw new RuntimeException("Remote driver error: " + response.message); - } else { - throw new RuntimeException("response code " + responseCode); - } + inputStream = connection.getErrorStream(); + } else { + inputStream = connection.getInputStream(); } - final InputStream inputStream = connection.getInputStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Response resp; + try { // Read the (serialized protobuf) response off the wire and convert it back to a Response - return translation.parseResponse(AvaticaUtils.readFullyToBytes(inputStream)); + resp = translation.parseResponse(AvaticaUtils.readFullyToBytes(inputStream)); } catch (IOException e) { + // Not a protobuf that we could parse. throw new RuntimeException(e); } + + // The server had an error, throw an Exception for that. + if (resp instanceof ErrorResponse) { + throw ((ErrorResponse) resp).toException(); + } + + return resp; } } http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java ---------------------------------------------------------------------- diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java index 298aa95..f661cbd 100644 --- a/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java +++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/RemoteService.java @@ -53,22 +53,19 @@ public class RemoteService extends JsonService { wr.close(); } } - int responseCode = connection.getResponseCode(); + final int responseCode = connection.getResponseCode(); + final InputStream inputStream; if (responseCode != HttpURLConnection.HTTP_OK) { - InputStream errorStream = connection.getErrorStream(); - if (errorStream != null) { - String errorResponse = AvaticaUtils.readFully(errorStream); - ErrorResponse response = decode(errorResponse, ErrorResponse.class); - throw new RuntimeException("Remote driver error: " + response.message); - } else { - throw new RuntimeException("response code " + responseCode); - } + inputStream = connection.getErrorStream(); + } else { + inputStream = connection.getInputStream(); } - final InputStream inputStream = connection.getInputStream(); + return AvaticaUtils.readFully(inputStream); } catch (IOException e) { throw new RuntimeException(e); } + } } http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java ---------------------------------------------------------------------- diff --git a/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java b/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java index 3717ab5..2309661 100644 --- a/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java +++ b/avatica/src/main/java/org/apache/calcite/avatica/remote/Service.java @@ -16,7 +16,9 @@ */ package org.apache.calcite.avatica.remote; +import org.apache.calcite.avatica.AvaticaClientRuntimeException; import org.apache.calcite.avatica.AvaticaConnection; +import org.apache.calcite.avatica.AvaticaSeverity; import org.apache.calcite.avatica.ConnectionPropertiesImpl; import org.apache.calcite.avatica.Meta; import org.apache.calcite.avatica.proto.Common; @@ -30,11 +32,16 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Message; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; /** * API for request-response calls to an Avatica server. @@ -113,7 +120,8 @@ public interface Service { name = "closeConnection"), @JsonSubTypes.Type(value = ConnectionSyncResponse.class, name = "connectionSync"), @JsonSubTypes.Type(value = DatabasePropertyResponse.class, name = "databaseProperties"), - @JsonSubTypes.Type(value = ExecuteResponse.class, name = "executeResults") }) + @JsonSubTypes.Type(value = ExecuteResponse.class, name = "executeResults"), + @JsonSubTypes.Type(value = ErrorResponse.class, name = "error") }) abstract class Response { abstract Response deserialize(Message genericMsg); abstract Message serialize(); @@ -1924,8 +1932,7 @@ public interface Service { return service.apply(this); } - @Override - Request deserialize(Message genericMsg) { + @Override Request deserialize(Message genericMsg) { if (!(genericMsg instanceof Requests.OpenConnectionRequest)) { throw new IllegalArgumentException( "Expected OpenConnectionRequest, but got" + genericMsg.getClass().getName()); @@ -1948,8 +1955,7 @@ public interface Service { return new OpenConnectionRequest(connectionId, info); } - @Override - Message serialize() { + @Override Message serialize() { Requests.OpenConnectionRequest.Builder builder = Requests.OpenConnectionRequest.newBuilder(); if (null != connectionId) { builder.setConnectionId(connectionId); @@ -2112,6 +2118,7 @@ public interface Service { /** Response from * {@link org.apache.calcite.avatica.remote.Service.CloseConnectionRequest}. */ class CloseConnectionResponse extends Response { + @JsonCreator public CloseConnectionResponse() {} @@ -2438,74 +2445,149 @@ public interface Service { } /** - * ErrorResponse can be used in response to any kind of request. It is used internally - * by the transport layers to format errors for transport over the wire. - * Thus, {@link Request#apply} will never return an ErrorResponse. + * Response for any request that the server failed to successfully perform. + * It is used internally by the transport layers to format errors for + * transport over the wire. Thus, {@link Request#apply} will never return + * an ErrorResponse. */ - class ErrorResponse extends Response { - public final String message; + public class ErrorResponse extends Response { + public static final int UNKNOWN_ERROR_CODE = -1; + public static final String UNKNOWN_SQL_STATE = "00000"; + + public final List<String> exceptions; + public final String errorMessage; + public final int errorCode; + public final String sqlState; + public final AvaticaSeverity severity; - public ErrorResponse() { - message = null; + ErrorResponse() { + exceptions = Collections.singletonList("Unhandled exception"); + errorMessage = "Unknown message"; + errorCode = -1; + sqlState = UNKNOWN_SQL_STATE; + severity = AvaticaSeverity.UNKNOWN; } @JsonCreator - public ErrorResponse(@JsonProperty("message") String message) { - this.message = message; + public ErrorResponse(@JsonProperty("exceptions") List<String> exceptions, + @JsonProperty("errorMessage") String errorMessage, + @JsonProperty("errorCode") int errorCode, + @JsonProperty("sqlState") String sqlState, + @JsonProperty("severity") AvaticaSeverity severity) { + this.exceptions = exceptions; + this.errorMessage = errorMessage; + this.errorCode = errorCode; + this.sqlState = sqlState; + this.severity = severity; + } + + protected ErrorResponse(Exception e, String errorMessage, int code, String sqlState, + AvaticaSeverity severity) { + this(errorMessage, code, sqlState, severity, toStackTraces(e)); + } + + protected ErrorResponse(String errorMessage, int code, String sqlState, + AvaticaSeverity severity, List<String> exceptions) { + this.exceptions = exceptions; + this.errorMessage = errorMessage; + this.errorCode = code; + this.sqlState = sqlState; + this.severity = severity; + } + + static List<String> toStackTraces(Exception e) { + List<String> stackTraces = new ArrayList<>(); + stackTraces.add(toString(e)); + if (e instanceof SQLException) { + SQLException next = ((SQLException) e).getNextException(); + while (null != next) { + stackTraces.add(toString(next)); + next = next.getNextException(); + } + } + return stackTraces; + } + + static String toString(Exception e) { + StringWriter sw = new StringWriter(); + Objects.requireNonNull(e).printStackTrace(new PrintWriter(sw)); + return sw.toString(); } @Override ErrorResponse deserialize(Message genericMsg) { if (!(genericMsg instanceof Responses.ErrorResponse)) { - throw new IllegalArgumentException( - "Expected ErrorResponse, but got " + genericMsg.getClass().getName()); - } - - final Responses.ErrorResponse msg = (Responses.ErrorResponse) genericMsg; - final Descriptor desc = msg.getDescriptorForType(); - - String message = null; - if (ProtobufService.hasField(msg, desc, - Responses.ErrorResponse.MESSAGE_FIELD_NUMBER)) { - message = msg.getMessage(); + throw new IllegalArgumentException("Expected ErrorResponse, but got " + + genericMsg.getClass()); } - return new ErrorResponse(message); + Responses.ErrorResponse msg = (Responses.ErrorResponse) genericMsg; + return new ErrorResponse(msg.getExceptionsList(), msg.getErrorMessage(), + msg.getErrorCode(), msg.getSqlState(), AvaticaSeverity.fromProto(msg.getSeverity())); } @Override Responses.ErrorResponse serialize() { Responses.ErrorResponse.Builder builder = Responses.ErrorResponse.newBuilder(); - - if (null != message) { - builder.setMessage(message); - } - - return builder.build(); + return builder.addAllExceptions(exceptions).setErrorMessage(errorMessage) + .setErrorCode(errorCode).setSqlState(sqlState).setSeverity(severity.toProto()).build(); } @Override public int hashCode() { - return message == null ? 0 : message.hashCode(); + final int prime = 31; + int result = 1; + result = prime * result + ((exceptions == null) ? 0 : exceptions.hashCode()); + result = prime * result + errorCode; + result = prime * result + ((sqlState == null) ? 0 : sqlState.hashCode()); + result = prime * result + ((severity == null) ? 0 : severity.hashCode()); + return result; } - @Override public boolean equals(Object o) { - if (o == this) { + @Override public boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ErrorResponse)) { + return false; + } - if (o instanceof ErrorResponse) { - ErrorResponse other = (ErrorResponse) o; + ErrorResponse other = (ErrorResponse) obj; + if (exceptions == null) { + if (other.exceptions != null) { + return false; + } + } else if (!exceptions.equals(other.exceptions)) { + return false; + } - if (null == message) { - if (null != other.message) { - return false; - } - } else if (!message.equals(other.message)) { + if (errorMessage == null) { + if (other.errorMessage != null) { return false; } + } else if (!errorMessage.equals(other.errorMessage)) { + return false; + } - return true; + if (errorCode != other.errorCode) { + return false; } - return false; + if (sqlState == null) { + if (other.sqlState != null) { + return false; + } + } else if (!sqlState.equals(other.sqlState)) { + return false; + } + + if (severity != other.severity) { + return false; + } + + return true; + } + + public AvaticaClientRuntimeException toException() { + return new AvaticaClientRuntimeException("Remote driver error: " + errorMessage, errorCode, + sqlState, severity, exceptions); } } } http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/protobuf/common.proto ---------------------------------------------------------------------- diff --git a/avatica/src/main/protobuf/common.proto b/avatica/src/main/protobuf/common.proto index 05bb7c2..3716c7e 100644 --- a/avatica/src/main/protobuf/common.proto +++ b/avatica/src/main/protobuf/common.proto @@ -189,3 +189,12 @@ message TypedValue { double double_value = 6; // big numbers bool null = 7; // a null object } + +// The severity of some unexpected outcome to an operation. +// Protobuf enum values must be unique across all other enums +enum Severity { + UNKNOWN_SEVERITY = 0; + FATAL_SEVERITY = 1; + ERROR_SEVERITY = 2; + WARNING_SEVERITY = 3; +} http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/main/protobuf/responses.proto ---------------------------------------------------------------------- diff --git a/avatica/src/main/protobuf/responses.proto b/avatica/src/main/protobuf/responses.proto index e4f52a3..7c52be7 100644 --- a/avatica/src/main/protobuf/responses.proto +++ b/avatica/src/main/protobuf/responses.proto @@ -82,6 +82,11 @@ message DatabasePropertyResponse { repeated DatabasePropertyElement props = 1; } +// Send contextual information about some error over the wire from the server. message ErrorResponse { - string message = 1; + repeated string exceptions = 1; // exception stacktraces, many for linked exceptions. + string error_message = 2; // human readable description + Severity severity = 3; + uint32 error_code = 4; // numeric identifier for error + string sql_state = 5; // five-character standard-defined value } http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/test/java/org/apache/calcite/avatica/remote/AbstractHandlerTest.java ---------------------------------------------------------------------- diff --git a/avatica/src/test/java/org/apache/calcite/avatica/remote/AbstractHandlerTest.java b/avatica/src/test/java/org/apache/calcite/avatica/remote/AbstractHandlerTest.java new file mode 100644 index 0000000..012cccc --- /dev/null +++ b/avatica/src/test/java/org/apache/calcite/avatica/remote/AbstractHandlerTest.java @@ -0,0 +1,165 @@ +/* + * 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.Handler.HandlerResponse; +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 org.junit.Test; +import org.mockito.Mockito; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test for common functionality across {@link Handler} implementations. + */ +public class AbstractHandlerTest { + + private String exceptionToString(Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + Objects.requireNonNull(e).printStackTrace(pw); + return sw.toString(); + } + + @Test public void testExceptionUnwrappingWithoutContext() { + @SuppressWarnings("unchecked") + AbstractHandler<String> handler = Mockito.mock(AbstractHandler.class); + + Mockito.when(handler.unwrapException(Mockito.any(Exception.class))).thenCallRealMethod(); + + Exception e = new RuntimeException(); + Response resp = handler.unwrapException(e); + assertTrue("Response should be ErrorResponse, but was " + resp.getClass(), + resp instanceof ErrorResponse); + ErrorResponse errorResp = (ErrorResponse) resp; + assertEquals(ErrorResponse.UNKNOWN_ERROR_CODE, errorResp.errorCode); + assertEquals(AvaticaSeverity.UNKNOWN, errorResp.severity); + assertEquals(Arrays.asList(exceptionToString(e)), errorResp.exceptions); + + e = new AvaticaRuntimeException(); + resp = handler.unwrapException(e); + assertTrue("Response should be ErrorResponse, but was " + resp.getClass(), + resp instanceof ErrorResponse); + errorResp = (ErrorResponse) resp; + assertEquals(ErrorResponse.UNKNOWN_ERROR_CODE, errorResp.errorCode); + assertEquals(AvaticaSeverity.UNKNOWN, errorResp.severity); + assertEquals(Arrays.asList(exceptionToString(e)), errorResp.exceptions); + } + + @Test public void testExceptionUnwrappingWithContext() { + @SuppressWarnings("unchecked") + AbstractHandler<String> handler = Mockito.mock(AbstractHandler.class); + + Mockito.when(handler.unwrapException(Mockito.any(Exception.class))).thenCallRealMethod(); + + final String msg = "Something failed!"; + AvaticaRuntimeException e = new AvaticaRuntimeException(msg, + ErrorResponse.UNKNOWN_ERROR_CODE, ErrorResponse.UNKNOWN_SQL_STATE, AvaticaSeverity.FATAL); + Response resp = handler.unwrapException(e); + assertTrue("Response should be ErrorResponse, but was " + resp.getClass(), + resp instanceof ErrorResponse); + ErrorResponse errorResp = (ErrorResponse) resp; + assertEquals(ErrorResponse.UNKNOWN_ERROR_CODE, errorResp.errorCode); + assertEquals(AvaticaSeverity.FATAL, errorResp.severity); + assertEquals(Arrays.asList(exceptionToString(e)), errorResp.exceptions); + assertEquals(msg, errorResp.errorMessage); + } + + @Test public void testFailedResponseSerialization() throws IOException { + @SuppressWarnings("unchecked") + final AbstractHandler<String> handler = Mockito.mock(AbstractHandler.class); + final Request request = Mockito.mock(Request.class); + final Response response = Mockito.mock(Response.class); + final IOException exception = new IOException(); + final ErrorResponse errorResponse = Mockito.mock(ErrorResponse.class); + final String serializedErrorResponse = "An ErrorResponse"; + + // Accept a serialized request + Mockito.when(handler.apply(Mockito.anyString())).thenCallRealMethod(); + // Deserialize it back into a POJO + Mockito.when(handler.decode(Mockito.anyString())).thenReturn(request); + // Construct the Response for that Request + Mockito.when(request.accept(Mockito.any(Service.class))).thenReturn(response); + // Throw an IOException when serializing the Response. + Mockito.when(handler.encode(response)).thenThrow(exception); + // Convert the IOException into an ErrorResponse + Mockito.when(handler.unwrapException(exception)).thenReturn(errorResponse); + Mockito.when(handler.encode(errorResponse)).thenReturn(serializedErrorResponse); + + HandlerResponse<String> handlerResp = handler.apply("this is mocked out"); + assertEquals(500, handlerResp.getStatusCode()); + assertEquals(serializedErrorResponse, handlerResp.getResponse()); + } + + @Test public void testFailedErrorResponseSerialization() throws IOException { + @SuppressWarnings("unchecked") + final AbstractHandler<String> handler = Mockito.mock(AbstractHandler.class); + final Request request = Mockito.mock(Request.class); + final Response response = Mockito.mock(Response.class); + final IOException exception = new IOException(); + final ErrorResponse errorResponse = Mockito.mock(ErrorResponse.class); + + // Accept a serialized request + Mockito.when(handler.apply(Mockito.anyString())).thenCallRealMethod(); + // Deserialize it back into a POJO + Mockito.when(handler.decode(Mockito.anyString())).thenReturn(request); + // Construct the Response for that Request + Mockito.when(request.accept(Mockito.any(Service.class))).thenReturn(response); + // Throw an IOException when serializing the Response. + Mockito.when(handler.encode(response)).thenThrow(exception); + // Convert the IOException into an ErrorResponse + Mockito.when(handler.unwrapException(exception)).thenReturn(errorResponse); + // Fail to serialize the ErrorResponse + Mockito.when(handler.encode(errorResponse)).thenThrow(exception); + + try { + handler.apply("this is mocked out"); + } catch (RuntimeException e) { + assertEquals(exception, e.getCause()); + } + } + + @Test public void testFailedRequestDeserialization() throws IOException { + @SuppressWarnings("unchecked") + final AbstractHandler<String> handler = Mockito.mock(AbstractHandler.class); + final IOException exception = new IOException(); + + // Accept a serialized request + Mockito.when(handler.apply(Mockito.anyString())).thenCallRealMethod(); + // Throw an Exception trying to convert it back into a POJO + Mockito.when(handler.decode(Mockito.anyString())).thenThrow(exception); + + try { + handler.apply("this is mocked out"); + } catch (RuntimeException e) { + assertEquals(exception, e.getCause()); + } + } +} + +// End AbstractHandlerTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/test/java/org/apache/calcite/avatica/remote/ErrorResponseTest.java ---------------------------------------------------------------------- diff --git a/avatica/src/test/java/org/apache/calcite/avatica/remote/ErrorResponseTest.java b/avatica/src/test/java/org/apache/calcite/avatica/remote/ErrorResponseTest.java new file mode 100644 index 0000000..7ee6fcf --- /dev/null +++ b/avatica/src/test/java/org/apache/calcite/avatica/remote/ErrorResponseTest.java @@ -0,0 +1,63 @@ +/* + * 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.AvaticaClientRuntimeException; +import org.apache.calcite.avatica.AvaticaSeverity; +import org.apache.calcite.avatica.remote.Service.ErrorResponse; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * A test class for ErrorResponse. + */ +public class ErrorResponseTest { + + @Test public void testEquality() { + final String message = "There was an error"; + final int code = 23; + final String state = "a1b2c"; + final AvaticaSeverity severity = AvaticaSeverity.ERROR; + final List<String> exceptions = Arrays.asList("Server Stacktrace 1", "Server Stacktace 2"); + assertEquals(new ErrorResponse(message, code, state, severity, exceptions), + new ErrorResponse(message, code, state, severity, exceptions)); + } + + @Test public void testToClientRTE() { + final String message = "There was an error"; + final int code = 23; + final String state = "a1b2c"; + final AvaticaSeverity severity = AvaticaSeverity.ERROR; + final List<String> exceptions = Arrays.asList("Server Stacktrace 1", "Server Stacktace 2"); + final ErrorResponse resp = new ErrorResponse(message, code, state, severity, exceptions); + AvaticaClientRuntimeException exception = resp.toException(); + assertTrue("Expected error message to end with '" + resp.errorMessage + "', but was '" + + exception.getMessage() + "'", exception.getMessage().endsWith(resp.errorMessage)); + assertEquals(resp.errorCode, exception.getErrorCode()); + assertEquals(resp.severity, exception.getSeverity()); + assertEquals(resp.sqlState, exception.getSqlState()); + assertEquals(resp.exceptions, exception.getServerExceptions()); + } +} + +// End ErrorResponseTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufHandlerTest.java ---------------------------------------------------------------------- diff --git a/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufHandlerTest.java b/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufHandlerTest.java index 2e307ca..aa03c3c 100644 --- a/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufHandlerTest.java +++ b/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufHandlerTest.java @@ -21,6 +21,7 @@ import org.apache.calcite.avatica.Meta.Frame; import org.apache.calcite.avatica.proto.Common; import org.apache.calcite.avatica.proto.Requests; import org.apache.calcite.avatica.proto.Responses; +import org.apache.calcite.avatica.remote.Handler.HandlerResponse; import org.apache.calcite.avatica.remote.Service.FetchRequest; import org.apache.calcite.avatica.remote.Service.FetchResponse; @@ -90,7 +91,9 @@ public class ProtobufHandlerTest { when(translation.serializeResponse(response)) .thenReturn(response.serialize().toByteArray()); - byte[] serializedResponse = handler.apply(serializedRequest); + HandlerResponse<byte[]> handlerResponse = handler.apply(serializedRequest); + byte[] serializedResponse = handlerResponse.getResponse(); + assertEquals(200, handlerResponse.getStatusCode()); Responses.FetchResponse protoResponse = Responses.FetchResponse.parseFrom(serializedResponse); http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufTranslationImplTest.java ---------------------------------------------------------------------- diff --git a/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufTranslationImplTest.java b/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufTranslationImplTest.java index 337932e..d385054 100644 --- a/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufTranslationImplTest.java +++ b/avatica/src/test/java/org/apache/calcite/avatica/remote/ProtobufTranslationImplTest.java @@ -17,6 +17,7 @@ package org.apache.calcite.avatica.remote; import org.apache.calcite.avatica.AvaticaParameter; +import org.apache.calcite.avatica.AvaticaSeverity; import org.apache.calcite.avatica.ColumnMetaData; import org.apache.calcite.avatica.ColumnMetaData.Rep; import org.apache.calcite.avatica.ConnectionPropertiesImpl; @@ -60,6 +61,8 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; @@ -273,7 +276,11 @@ public class ProtobufTranslationImplTest<T> { new Meta.StatementHandle("connectionId", Integer.MAX_VALUE, signature))); - responses.add(new ErrorResponse("an error occurred")); + StringWriter sw = new StringWriter(); + new Exception().printStackTrace(new PrintWriter(sw)); + responses.add(new ErrorResponse(Collections.singletonList(sw.toString()), "Test Error Message", + ErrorResponse.UNKNOWN_ERROR_CODE, ErrorResponse.UNKNOWN_SQL_STATE, + AvaticaSeverity.WARNING)); return responses; } http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaClientRuntimeExceptionTest.java ---------------------------------------------------------------------- diff --git a/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaClientRuntimeExceptionTest.java b/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaClientRuntimeExceptionTest.java new file mode 100644 index 0000000..d457aa3 --- /dev/null +++ b/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaClientRuntimeExceptionTest.java @@ -0,0 +1,50 @@ +/* + * 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.test; + +import org.apache.calcite.avatica.AvaticaClientRuntimeException; +import org.apache.calcite.avatica.AvaticaSeverity; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Test class for {@link AvaticaClientRuntimeException}. + */ +public class AvaticaClientRuntimeExceptionTest { + + @Test public void testGetters() { + final String errorMsg = "My error message"; + final int errorCode = 10; + final String sqlState = "abc12"; + final AvaticaSeverity severity = AvaticaSeverity.ERROR; + final List<String> stacktraces = Arrays.asList("my stack trace"); + AvaticaClientRuntimeException e = new AvaticaClientRuntimeException(errorMsg, errorCode, + sqlState, severity, stacktraces); + assertEquals(errorMsg, e.getMessage()); + assertEquals(errorCode, e.getErrorCode()); + assertEquals(severity, e.getSeverity()); + assertEquals(stacktraces, e.getServerExceptions()); + } + +} + +// End AvaticaClientRuntimeExceptionTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSeverityTest.java ---------------------------------------------------------------------- diff --git a/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSeverityTest.java b/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSeverityTest.java new file mode 100644 index 0000000..945f959 --- /dev/null +++ b/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSeverityTest.java @@ -0,0 +1,39 @@ +/* + * 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.test; + +import org.apache.calcite.avatica.AvaticaSeverity; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link AvaticaSeverity}. + */ +public class AvaticaSeverityTest { + + @Test + public void testProtobufSerialization() { + for (AvaticaSeverity severity : AvaticaSeverity.values()) { + assertEquals(severity, AvaticaSeverity.fromProto(severity.toProto())); + } + } + +} + +// End AvaticaSeverityTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSqlExceptionTest.java ---------------------------------------------------------------------- diff --git a/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSqlExceptionTest.java b/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSqlExceptionTest.java new file mode 100644 index 0000000..fb10c45 --- /dev/null +++ b/avatica/src/test/java/org/apache/calcite/avatica/test/AvaticaSqlExceptionTest.java @@ -0,0 +1,49 @@ +/* + * 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.test; + +import org.apache.calcite.avatica.AvaticaSqlException; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link AvaticaSqlException}. + */ +public class AvaticaSqlExceptionTest { + + @Test public void testGetters() { + final String msg = "My query failed!"; + final int code = 42; + final String sql = "SELECT foo FROM bar;"; + final String stacktrace = "My Stack Trace"; + + AvaticaSqlException e = new AvaticaSqlException(msg, sql, code, Arrays.asList(stacktrace)); + assertTrue(e.getMessage().contains(msg)); + assertEquals(code, e.getErrorCode()); + assertEquals(sql, e.getSQLState()); + assertEquals(1, e.getStackTraces().size()); + assertEquals(stacktrace, e.getStackTraces().get(0)); + } + +} + +// End AvaticaSqlExceptionTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/5be93fb4/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java b/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java index 4147bcd..59a3bfa 100644 --- a/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java +++ b/core/src/test/java/org/apache/calcite/test/ExceptionMessageTest.java @@ -109,7 +109,7 @@ public class ExceptionMessageTest { fail("Query badEntries should result in an exception"); } catch (SQLException e) { assertThat(e.getMessage(), - equalTo("error while executing SQL \"select * from \"badEntries\"\": " + equalTo("Error while executing SQL \"select * from \"badEntries\"\": " + "Can't iterate over badEntries")); } } @@ -120,7 +120,7 @@ public class ExceptionMessageTest { fail("Query should fail"); } catch (SQLException e) { assertThat(e.getMessage(), - equalTo("error while executing SQL \"invalid sql\": parse failed: " + equalTo("Error while executing SQL \"invalid sql\": parse failed: " + "Non-query expression encountered in illegal context")); } }
