[CALCITE-2845] Avoid duplication of exception messages
diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
index b3552f8..e28fe46 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaConnection.java
@@ -552,9 +552,11 @@
statement.updateCount = metaResultSet.updateCount;
signature2 = executeResult.resultSets.get(0).signature;
}
+ } catch (SQLException e) {
+ // We don't add meaningful info yet, so just rethrow the original exception
+ throw e;
} catch (Exception e) {
- e.printStackTrace();
- throw HELPER.createException(e.getMessage(), e);
+ throw HELPER.createException("Error while executing a prepared statement", e);
}
final TimeZone timeZone = getTimeZone();
@@ -573,9 +575,12 @@
statement.openResultSet.execute();
isUpdateCapable(statement);
}
+ } catch (SQLException e) {
+ // We don't add meaningful info yet, so just rethrow the original exception
+ throw e;
} catch (Exception e) {
throw HELPER.createException(
- "exception while executing query: " + e.getMessage(), e);
+ "Error while executing a resultset", e);
}
return statement.openResultSet;
}
diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
index bb02f1f..af71381 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java
@@ -82,7 +82,10 @@
}
void printServerStackTrace(PrintStreamOrWriter streamOrWriter) {
- for (String serverStackTrace : this.stackTraces) {
+ List<String> traces = this.stackTraces;
+ for (int i = 0; i < traces.size(); i++) {
+ String serverStackTrace = traces.get(i);
+ streamOrWriter.println("Remote driver error #" + i + ":");
streamOrWriter.println(serverStackTrace);
}
}
diff --git a/core/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java b/core/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
index 2d3c756..ac76ce3 100644
--- a/core/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
+++ b/core/src/main/java/org/apache/calcite/avatica/AvaticaStatement.java
@@ -160,8 +160,8 @@
}
}
} catch (RuntimeException e) {
- throw AvaticaConnection.HELPER.createException("Error while executing SQL \"" + sql + "\": "
- + e.getMessage(), e);
+ throw AvaticaConnection.HELPER.createException(
+ "Error while executing SQL \"" + sql + "\"", e);
}
throw new RuntimeException("Failed to successfully execute query after "
@@ -231,8 +231,8 @@
}
return openResultSet;
} catch (RuntimeException e) {
- throw AvaticaConnection.HELPER.createException("Error while executing SQL \"" + sql + "\": "
- + e.getMessage(), e);
+ throw AvaticaConnection.HELPER.createException(
+ "Error while executing SQL \"" + sql + "\"", e);
}
}
diff --git a/core/src/main/java/org/apache/calcite/avatica/Helper.java b/core/src/main/java/org/apache/calcite/avatica/Helper.java
index fe716db..215e2b9 100644
--- a/core/src/main/java/org/apache/calcite/avatica/Helper.java
+++ b/core/src/main/java/org/apache/calcite/avatica/Helper.java
@@ -50,7 +50,10 @@
if (null != rte.getRpcMetadata()) {
serverAddress = rte.getRpcMetadata().serverAddress;
}
- return new AvaticaSqlException(message, rte.getSqlState(), rte.getErrorCode(),
+ // Note: we don't add "e" as a cause, so we add its message to the message of
+ // the newly created exception
+ return new AvaticaSqlException(message + ". " + e.getMessage(),
+ rte.getSqlState(), rte.getErrorCode(),
rte.getServerExceptions(), serverAddress);
}
return new SQLException(message, e);
diff --git a/core/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java b/core/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java
index b291d4c..ab9c7c6 100644
--- a/core/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java
+++ b/core/src/main/java/org/apache/calcite/avatica/remote/AbstractHandler.java
@@ -145,7 +145,7 @@
* @param e The Exception to summarize.
* @return A summary message for the Exception.
*/
- private String getCausalChain(Exception e) {
+ private String getCausalChain(Throwable e) {
StringBuilder sb = new StringBuilder(16);
Throwable curr = e;
// Could use Guava, but that would increase dependency set unnecessarily.
@@ -156,6 +156,17 @@
String message = curr.getMessage();
sb.append(curr.getClass().getSimpleName()).append(": ");
sb.append(null == message ? NULL_EXCEPTION_MESSAGE : message);
+ Throwable[] suppressed = curr.getSuppressed();
+ if (suppressed.length > 0) {
+ sb.append(", suppressed: [");
+ String sep = "";
+ for (Throwable throwable : suppressed) {
+ sb.append(sep);
+ sb.append(getCausalChain(throwable));
+ sep = ", ";
+ }
+ sb.append("]");
+ }
curr = curr.getCause();
}
if (sb.length() == 0) {
diff --git a/server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java b/server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
index b8f4ea4..2a8843c 100644
--- a/server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
+++ b/server/src/main/java/org/apache/calcite/avatica/jdbc/JdbcMeta.java
@@ -304,7 +304,7 @@
}
return map;
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -315,7 +315,7 @@
try {
propertyValue = p.method.invoke(metaData);
} catch (IllegalAccessException | InvocationTargetException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
} else {
propertyValue = p.defaultValue;
@@ -333,7 +333,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -359,7 +359,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -370,7 +370,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -380,7 +380,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -390,7 +390,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -403,7 +403,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -416,7 +416,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -429,7 +429,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -442,7 +442,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -457,7 +457,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -470,7 +470,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -483,7 +483,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -509,7 +509,7 @@
int stmtId = registerMetaStatement(rs);
return JdbcResultSet.create(ch.id, stmtId, rs);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -630,7 +630,7 @@
throw new RuntimeException("Connection already exists: " + ch.id);
}
} catch (SQLException e) {
- throw new RuntimeException(e);
+ throw propagate(e);
}
}
@@ -691,14 +691,9 @@
}
}
- RuntimeException propagate(Throwable e) {
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- } else if (e instanceof Error) {
- throw (Error) e;
- } else {
- throw new RuntimeException(e);
- }
+ static <E extends Throwable> RuntimeException propagate(Throwable e) throws E {
+ // We have nothing to add, so just throw the original exception
+ throw (E) e;
}
public StatementHandle prepare(ConnectionHandle ch, String sql,
diff --git a/server/src/test/java/org/apache/calcite/avatica/ExceptionUtils.java b/server/src/test/java/org/apache/calcite/avatica/ExceptionUtils.java
new file mode 100644
index 0000000..eff382f
--- /dev/null
+++ b/server/src/test/java/org/apache/calcite/avatica/ExceptionUtils.java
@@ -0,0 +1,44 @@
+/*
+ * 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.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Objects;
+
+/** Prints full stacktrace of {@link java.lang.Throwable} as a {@code String}. */
+public class ExceptionUtils {
+ private ExceptionUtils() {
+ // The constructor is extremely secret
+ }
+
+ /** Prints full stacktrace of {@link java.lang.Throwable} as a {@code String}.
+ * @param e exception
+ * @return string representation of a given exception
+ */
+ public static String toString(Exception e) {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ Objects.requireNonNull(e);
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ pw.flush();
+ return sw.toString();
+ }
+}
+
+// End ExceptionUtils.java
diff --git a/server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java b/server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java
index 66fcf8c..4d473ef 100644
--- a/server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/RemoteDriverTest.java
@@ -67,11 +67,12 @@
import java.util.UUID;
import java.util.concurrent.Callable;
+import static org.apache.calcite.avatica.test.StringContainsOnce.containsStringOnce;
+
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.core.StringContains.containsString;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -914,8 +915,9 @@
fail("expected error, got " + resultSet);
} catch (SQLException e) {
LOG.info("Caught expected error", e);
- assertThat(e.getMessage(),
- containsString("exception while executing query: unbound parameter"));
+ String message = ExceptionUtils.toString(e);
+ assertThat(message,
+ containsStringOnce("unbound parameter"));
}
final ParameterMetaData parameterMetaData = ps.getParameterMetaData();
@@ -1842,8 +1844,9 @@
@Override public Response _apply(Request request) {
final RequestLogger logger = THREAD_LOG.get();
+ String jsonRequest = null;
try {
- String jsonRequest = JsonService.MAPPER.writeValueAsString(request);
+ jsonRequest = JsonService.MAPPER.writeValueAsString(request);
logger.requestStart(jsonRequest);
Response response = super._apply(request);
@@ -1853,7 +1856,7 @@
return response;
} catch (Exception e) {
- throw new RuntimeException(e);
+ throw new RuntimeException("Request " + jsonRequest + " failed", e);
}
}
}
diff --git a/server/src/test/java/org/apache/calcite/avatica/jdbc/JdbcMetaTest.java b/server/src/test/java/org/apache/calcite/avatica/jdbc/JdbcMetaTest.java
index f3ebc8a..0c80f44 100644
--- a/server/src/test/java/org/apache/calcite/avatica/jdbc/JdbcMetaTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/jdbc/JdbcMetaTest.java
@@ -50,13 +50,11 @@
@Test public void testExceptionPropagation() throws SQLException {
JdbcMeta meta = new JdbcMeta("url");
final Throwable e = new Exception();
- final RuntimeException rte;
try {
meta.propagate(e);
fail("Expected an exception to be thrown");
- } catch (RuntimeException caughtException) {
- rte = caughtException;
- assertThat(rte.getCause(), is(e));
+ } catch (Throwable caughtException) {
+ assertThat(caughtException, is(e));
}
}
diff --git a/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java b/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
index 2a1fe80..452309c 100644
--- a/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/remote/RemoteMetaTest.java
@@ -329,10 +329,8 @@
DriverManager.getConnection(url, "john", "doe");
fail("expected exception");
} catch (RuntimeException e) {
- assertEquals("Remote driver error: RuntimeException: "
- + "java.sql.SQLInvalidAuthorizationSpecException: invalid authorization specification"
- + " - not found: john"
- + " -> SQLInvalidAuthorizationSpecException: invalid authorization specification - "
+ assertEquals("Remote driver error: "
+ + "SQLInvalidAuthorizationSpecException: invalid authorization specification - "
+ "not found: john"
+ " -> HsqlException: invalid authorization specification - not found: john",
e.getMessage());
@@ -357,9 +355,8 @@
stmt2.executeQuery("select * from buffer");
fail("expected exception");
} catch (Exception e) {
- assertEquals("Error -1 (00000) : Error while executing SQL \"select * from buffer\": "
- + "Remote driver error: RuntimeException: java.sql.SQLSyntaxErrorException: "
- + "user lacks privilege or object not found: BUFFER -> "
+ assertEquals("Error -1 (00000) : Error while executing SQL \"select * from buffer\". "
+ + "Remote driver error: "
+ "SQLSyntaxErrorException: user lacks privilege or object not found: BUFFER -> "
+ "HsqlException: user lacks privilege or object not found: BUFFER",
e.getMessage());
diff --git a/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java b/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java
index 9f9914a..e11f1cb 100644
--- a/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/server/BasicAuthHttpServerTest.java
@@ -149,10 +149,8 @@
readWriteData(url, "DISALLOWED_DB_USER", props);
fail("Expected an exception");
} catch (RuntimeException e) {
- assertEquals("Remote driver error: RuntimeException: "
- + "java.sql.SQLInvalidAuthorizationSpecException: invalid authorization specification"
- + " - not found: USER1"
- + " -> SQLInvalidAuthorizationSpecException: invalid authorization specification - "
+ assertEquals("Remote driver error: "
+ + "SQLInvalidAuthorizationSpecException: invalid authorization specification - "
+ "not found: USER1"
+ " -> HsqlException: invalid authorization specification - not found: USER1",
e.getMessage());
diff --git a/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java b/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java
index a5d8e86..73b5bb5 100644
--- a/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java
+++ b/server/src/test/java/org/apache/calcite/avatica/server/DigestAuthHttpServerTest.java
@@ -163,10 +163,8 @@
readWriteData(url, "DISALLOWED_HSQLDB_USER", props);
fail("Expected a failure");
} catch (RuntimeException e) {
- assertEquals("Remote driver error: RuntimeException: "
- + "java.sql.SQLInvalidAuthorizationSpecException: invalid authorization specification"
- + " - not found: USER1"
- + " -> SQLInvalidAuthorizationSpecException: invalid authorization specification - "
+ assertEquals("Remote driver error: "
+ + "SQLInvalidAuthorizationSpecException: invalid authorization specification - "
+ "not found: USER1"
+ " -> HsqlException: invalid authorization specification - not found: USER1",
e.getMessage());
diff --git a/server/src/test/java/org/apache/calcite/avatica/test/StringContainsOnce.java b/server/src/test/java/org/apache/calcite/avatica/test/StringContainsOnce.java
new file mode 100644
index 0000000..3a55c1e
--- /dev/null
+++ b/server/src/test/java/org/apache/calcite/avatica/test/StringContainsOnce.java
@@ -0,0 +1,75 @@
+/*
+ * 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.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.core.SubstringMatcher;
+
+/**
+ * Tests if the argument is a string that contains a substring exactly once.
+ */
+public class StringContainsOnce extends SubstringMatcher {
+ public StringContainsOnce(String substring) {
+ super(substring);
+ }
+
+ @Override public void describeMismatchSafely(String item, Description mismatchDescription) {
+ int cnt = countMatches(item);
+ if (cnt == 0) {
+ mismatchDescription.appendText("pattern is not found in \"");
+ } else if (cnt == 2) {
+ mismatchDescription.appendText("pattern is present more than once in \"");
+ }
+ mismatchDescription.appendText(item);
+ mismatchDescription.appendText("\"");
+ }
+
+ @Override protected boolean evalSubstringOf(String s) {
+ return countMatches(s) == 1;
+ }
+
+ private int countMatches(String s) {
+ int indexOf = s.indexOf(substring);
+ if (indexOf < 0) {
+ return 0;
+ }
+ // There should be just a single match
+ return s.indexOf(substring, indexOf + 1) == -1 ? 1 : 2;
+ }
+
+ @Override protected String relationship() {
+ return "containing exactly once";
+ }
+
+ /**
+ * Creates a matcher that matches if the examined {@link String} contains the specified
+ * {@link String} anywhere exactly once.
+ *
+ * <p>For example:
+ * <pre>assertThat("myStringOfNote", containsStringOnce("ring"))</pre>
+ * @param substring substring
+ * @return matcher
+ */
+ @Factory
+ public static Matcher<String> containsStringOnce(String substring) {
+ return new StringContainsOnce(substring);
+ }
+}
+
+// End StringContainsOnce.java