[CALCITE-951] Print the server-side stack in the local exception (Josh Elser)
The AvaticaSqlException has a string representation of the stack trace from the server, but didn't explicitly add it when printing its own stacktrace. Close apache/calcite#165 Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/b82704f8 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/b82704f8 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/b82704f8 Branch: refs/heads/master Commit: b82704f8c1a1559b4b2a04315ae934851a4a74eb Parents: 2fd8c5a Author: Josh Elser <[email protected]> Authored: Wed Nov 4 12:04:38 2015 -0500 Committer: Julian Hyde <[email protected]> Committed: Wed Nov 4 11:07:50 2015 -0800 ---------------------------------------------------------------------- .../calcite/avatica/remote/RemoteMetaTest.java | 70 +++++++++++++------- .../calcite/avatica/AvaticaSqlException.java | 69 +++++++++++++++++++ 2 files changed, 114 insertions(+), 25 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/b82704f8/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 c68f3cf..d493c01 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 @@ -31,9 +31,11 @@ import org.apache.calcite.avatica.server.Main; import org.apache.calcite.avatica.server.Main.HandlerFactory; import org.apache.calcite.avatica.util.ArrayImpl; +import com.google.common.base.Throwables; import com.google.common.cache.Cache; import org.eclipse.jetty.server.handler.AbstractHandler; + import org.junit.AfterClass; import org.junit.Ignore; import org.junit.Test; @@ -59,6 +61,7 @@ import java.util.Random; import java.util.UUID; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -76,33 +79,14 @@ public class RemoteMetaTest { // Keep a reference to the servers we start to clean them up after private static final List<HttpServer> ACTIVE_SERVERS = new ArrayList<>(); - /** Factory that provides a {@link JdbcMeta}. */ - public static class FullyRemoteJdbcMetaFactory implements Meta.Factory { - - private static JdbcMeta instance = null; - - private static JdbcMeta getInstance() { - if (instance == null) { - try { - instance = new JdbcMeta(CONNECTION_SPEC.url, CONNECTION_SPEC.username, - CONNECTION_SPEC.password); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - return instance; - } - - @Override public Meta create(List<String> args) { - return getInstance(); - } - } + private final HttpServer server; + private final String url; @Parameters public static List<Object[]> parameters() throws Exception { List<Object[]> params = new ArrayList<>(); - final String[] mainArgs = new String[] { FullyRemoteJdbcMetaFactory.class.getName() }; + final String[] mainArgs = { FullyRemoteJdbcMetaFactory.class.getName() }; // Bind to '0' to pluck an ephemeral port instead of expecting a certain one to be free @@ -126,9 +110,6 @@ public class RemoteMetaTest { return params; } - private final HttpServer server; - private final String url; - public RemoteMetaTest(HttpServer server, Driver.Serialization serialization) { this.server = server; final int port = server.getPort(); @@ -449,6 +430,45 @@ public class RemoteMetaTest { ConnectionSpec.getDatabaseLock().unlock(); } } + + @Test public void testLocalStackTraceHasServerStackTrace() { + ConnectionSpec.getDatabaseLock().lock(); + try { + Statement statement = DriverManager.getConnection(url).createStatement(); + statement.executeQuery("SELECT * FROM BOGUS_TABLE_DEF_DOESNT_EXIST"); + } catch (SQLException e) { + // Verify that we got the expected exception + assertThat(e, instanceOf(AvaticaSqlException.class)); + + // Attempt to verify that we got a "server-side" class in the stack. + assertThat(Throwables.getStackTraceAsString(e), + containsString(JdbcMeta.class.getName())); + } finally { + ConnectionSpec.getDatabaseLock().unlock(); + } + } + + /** Factory that provides a {@link JdbcMeta}. */ + public static class FullyRemoteJdbcMetaFactory implements Meta.Factory { + + private static JdbcMeta instance = null; + + private static JdbcMeta getInstance() { + if (instance == null) { + try { + instance = new JdbcMeta(CONNECTION_SPEC.url, CONNECTION_SPEC.username, + CONNECTION_SPEC.password); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + return instance; + } + + @Override public Meta create(List<String> args) { + return getInstance(); + } + } } // End RemoteMetaTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/b82704f8/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 index 9782581..cfcf305 100644 --- a/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java +++ b/avatica/src/main/java/org/apache/calcite/avatica/AvaticaSqlException.java @@ -16,6 +16,8 @@ */ package org.apache.calcite.avatica; +import java.io.PrintStream; +import java.io.PrintWriter; import java.sql.SQLException; import java.util.List; import java.util.Objects; @@ -54,6 +56,73 @@ public class AvaticaSqlException extends SQLException { public List<String> getStackTraces() { return stackTraces; } + + // printStackTrace() will get redirected to printStackTrace(PrintStream), don't need to override. + + @Override public void printStackTrace(PrintStream stream) { + super.printStackTrace(stream); + stream.flush(); + printServerStackTrace(new PrintStreamOrWriter(stream)); + } + + @Override public void printStackTrace(PrintWriter writer) { + super.printStackTrace(writer); + writer.flush(); + printServerStackTrace(new PrintStreamOrWriter(writer)); + } + + void printServerStackTrace(PrintStreamOrWriter streamOrWriter) { + for (String serverStackTrace : this.stackTraces) { + streamOrWriter.println(serverStackTrace); + } + } + + /** + * A class that encapsulates either a PrintStream or a PrintWriter. + */ + private static class PrintStreamOrWriter { + /** + * Enumeration to differentiate between a PrintStream and a PrintWriter. + */ + private enum Type { + STREAM, + WRITER + } + + private PrintStream stream; + private PrintWriter writer; + private final Type type; + + public PrintStreamOrWriter(PrintStream stream) { + this.stream = stream; + type = Type.STREAM; + } + + public PrintStreamOrWriter(PrintWriter writer) { + this.writer = writer; + type = Type.WRITER; + } + + /** + * Prints the given string to the the provided stream or writer. + * + * @param string The string to print + */ + public void println(String string) { + switch (type) { + case STREAM: + stream.println(string); + stream.flush(); + return; + case WRITER: + writer.println(string); + writer.flush(); + return; + default: + throw new IllegalStateException(); + } + } + } } // End AvaticaSqlException.java
