Mercurial > hg > monetdb-java
changeset 700:940e266eeccd
Refactor BufferedMCLReader
It used to inherit from BufferedReader but there is no reason for that.
Also, it used to have a method readLine() which
- returned the line read
- stored the linetype
In the new setup we have a method advance() which reads
a line and stores both it and its type.
This makes the code more regular and makes it possible to
peek ahead without consuming.
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Thu, 08 Dec 2022 15:59:17 +0100 (2022-12-08) |
parents | 0ff364f569a1 |
children | f89882b07614 |
files | src/main/java/org/monetdb/jdbc/MonetConnection.java src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java src/main/java/org/monetdb/mcl/net/MapiSocket.java src/main/java/org/monetdb/merovingian/Control.java src/main/java/org/monetdb/util/SQLRestore.java tests/JDBC_API_Tester.java tests/SQLcopyinto.java |
diffstat | 8 files changed, 110 insertions(+), 114 deletions(-) [+] |
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java +++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java @@ -343,7 +343,7 @@ public class MonetConnection in = server.getReader(); out = server.getWriter(); - final String error = in.waitForPrompt(); + final String error = in.discardRemainder(); if (error != null) throw new SQLNonTransientConnectionException((error.length() > 6) ? error.substring(6) : error, "08001"); } catch (java.net.UnknownHostException e) { @@ -2138,7 +2138,7 @@ public class MonetConnection out.writeLine(queryTempl[0] + command + queryTempl[1]); else out.writeLine(commandTempl[0] + command + commandTempl[1]); - final String error = in.waitForPrompt(); + final String error = in.discardRemainder(); if (error != null) throw new SQLException(error.substring(6), error.substring(0, 5)); } catch (SocketTimeoutException e) { @@ -3131,7 +3131,7 @@ public class MonetConnection // have the prompt it is possible (and most likely) that we // already have the prompt and do not have to skip any // lines. Ignore errors from previous result sets. - in.waitForPrompt(); + in.discardRemainder(); // {{{ set reply size /** @@ -3158,16 +3158,15 @@ public class MonetConnection out.writeLine(templ[0] + query + templ[1]); // go for new results - String tmpLine = in.readLine(); - LineType linetype = in.getLineType(); + in.advance(); Response res = null; - while (linetype != LineType.PROMPT) { + while (in.getLineType() != LineType.PROMPT) { // each response should start with a start of header (or error) - switch (linetype) { + switch (in.getLineType()) { case SOHEADER: // make the response object, and fill it try { - switch (sohp.parse(tmpLine)) { + switch (sohp.parse(in.getLine())) { case StartOfHeaderParser.Q_PARSE: throw new MCLParseException("Q_PARSE header not allowed here", 1); case StartOfHeaderParser.Q_TABLE: @@ -3228,31 +3227,29 @@ public class MonetConnection final int offset = e.getErrorOffset(); error = "M0M10!error while parsing start of header:\n" + e.getMessage() + - " found: '" + tmpLine.charAt(offset) + - "' in: \"" + tmpLine + + " found: '" + in.getLine().charAt(offset) + + "' in: \"" + in.getLine() + "\" at pos: " + offset; // flush all the rest - in.waitForPrompt(); - linetype = in.getLineType(); + in.discardRemainder(); break; } // immediately handle errors after parsing the header (res may be null) if (error != null) { - in.waitForPrompt(); - linetype = in.getLineType(); + in.discardRemainder(); break; } // here we have a res object, which we can start filling while (res.wantsMore()) { - error = res.addLine(in.readLine(), in.getLineType()); + in.advance(); + error = res.addLine(in.getLine(), in.getLineType()); if (error != null) { // right, some protocol violation, // skip the rest of the result error = "M0M10!" + error; - in.waitForPrompt(); - linetype = in.getLineType(); + in.discardRemainder(); break; } } @@ -3266,21 +3263,20 @@ public class MonetConnection // read the next line (can be prompt, new result, error, etc.) // before we start the loop over - tmpLine = in.readLine(); - linetype = in.getLineType(); + in.advance(); break; case INFO: - addWarning(tmpLine.substring(1), "01000"); + addWarning(in.getLine().substring(1), "01000"); // read the next line (can be prompt, new result, error, etc.) // before we start the loop over - tmpLine = in.readLine(); - linetype = in.getLineType(); + in.advance(); break; case FILETRANSFER: // Consume the command - final String transferCommand = in.readLine(); + in.advance(); + final String transferCommand = in.getLine(); // Consume the fake prompt inserted by MapiSocket. - in.readLine(); + in.advance(); // Handle the request if (transferCommand != null) error = handleTransfer(transferCommand); @@ -3289,27 +3285,21 @@ public class MonetConnection // Then prepare for the next iteration if (error != null) { out.writeLine(error + "\n"); - error = in.waitForPrompt(); + error = in.discardRemainder(); } else { - tmpLine = in.readLine(); + in.advance(); } - linetype = in.getLineType(); break; - default: // Yeah... in Java this is correct! + default: // we have something we don't expect/understand, let's make it an error message - tmpLine = "!M0M10!protocol violation, unexpected " + linetype + " line: " + tmpLine; - // don't break; fall through... + String msg = "M0M10!protocol violation, unexpected " + in.getLineType() + " line: " + in.getLine(); + error = in.discardRemainder(msg); + break; case ERROR: // read everything till the prompt (should be // error) we don't know if we ignore some // garbage here... but the log should reveal that - error = in.waitForPrompt(); - linetype = in.getLineType(); - if (error != null) { - error = tmpLine.substring(1) + "\n" + error; - } else { - error = tmpLine.substring(1); - } + error = in.discardRemainder(in.getLine().substring(1)); break; } // end of switch (linetype) } // end of while (linetype != LineType.PROMPT)
--- a/src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java +++ b/src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java @@ -40,9 +40,10 @@ import java.io.UnsupportedEncodingExcept * @see org.monetdb.mcl.net.MapiSocket * @see org.monetdb.mcl.io.BufferedMCLWriter */ -public final class BufferedMCLReader extends BufferedReader { +public final class BufferedMCLReader /* extends BufferedReader */ { - /** The type of the last line read */ + private final BufferedReader inner; + private String current = null; private LineType lineType = LineType.UNKNOWN; /** @@ -52,7 +53,7 @@ public final class BufferedMCLReader ext * @param in A Reader */ public BufferedMCLReader(final Reader in) { - super(in); + inner = new BufferedReader(in); } /** @@ -66,47 +67,37 @@ public final class BufferedMCLReader ext public BufferedMCLReader(final InputStream in, final String enc) throws UnsupportedEncodingException { - super(new java.io.InputStreamReader(in, enc)); + this(new java.io.InputStreamReader(in, enc)); + } + + public void advance() throws IOException { + if (lineType == LineType.PROMPT) + return; + + current = inner.readLine(); + lineType = LineType.classify(current); + if (lineType == LineType.ERROR && current != null && !current.matches("^![0-9A-Z]{5}!.+")) { + current = "!22000!" + current.substring(1); + } } /** - * Read a line of text. A line is considered to be terminated by - * any one of a line feed ('\n'), a carriage return ('\r'), or a - * carriage return followed immediately by a linefeed. Before this - * method returns, it sets the linetype to any of the in MCL - * recognised line types. - * - * Warning: until the server properly prefixes all of its error - * messages with SQLSTATE codes, this method prefixes all errors it - * sees without sqlstate with the generic data exception code (22000). - * - * @return A String containing the contents of the line, not - * including any line-termination characters, or null if the - * end of the stream has been reached - * @throws IOException If an I/O error occurs + * Resets the linetype to UNKNOWN. */ - @Override - public String readLine() throws IOException { - String r = super.readLine(); - setLineType(r); - if (lineType == LineType.ERROR && r != null && !r.matches("^![0-9A-Z]{5}!.+")) { - r = "!22000!" + r.substring(1); - } - return r; + public void resetLineType() { + lineType = LineType.UNKNOWN; } /** - * Sets the linetype to the type of the string given. If the string - * is null, lineType is set to UNKNOWN. - * - * @param line the string to examine + * Return the current line, or null if we're at the end or before the beginning. + * @return the current line or null */ - public void setLineType(final String line) { - lineType = LineType.classify(line); + public String getLine() { + return current; } /** - * getLineType returns the type of the last line read. + * getLineType returns the type of the current line. * * @return Linetype representing the kind of line this is, one of the * following enums: UNKNOWN, HEADER, ERROR, RESULT, @@ -117,35 +108,52 @@ public final class BufferedMCLReader ext } /** - * Reads up till the MonetDB prompt, indicating the server is ready - * for a new command. All read data is discarded. If the last line - * read by readLine() was a prompt, this method will immediately return. - * - * If there are errors present in the lines that are read, then they - * are put in one string and returned <b>after</b> the prompt has - * been found. If no errors are present, null will be returned. + * Discard the remainder of the response but collect any further error messages. * * @return a string containing error messages, or null if there aren't any * @throws IOException if an IO exception occurs while talking to the server * * TODO(Wouter): should probably not have to be synchronized. */ - final public synchronized String waitForPrompt() throws IOException { - StringBuilder errmsgs = null; - String tmp; + + + final public synchronized String discardRemainder() throws IOException { + return discard(null); + } + + final public synchronized String discardRemainder(String error) throws IOException { + final StringBuilder sb; + if (error != null) { + sb = makeErrorBuffer(); + sb.append(error); + } else { + sb = null; + } + return discard(sb); + } + + final synchronized String discard(StringBuilder errmsgs) throws IOException { while (lineType != LineType.PROMPT) { - tmp = readLine(); - if (tmp == null) + advance(); + if (getLine() == null) throw new IOException("Connection to server lost!"); - if (lineType == LineType.ERROR) { + if (getLineType() == LineType.ERROR) { if (errmsgs == null) errmsgs = new StringBuilder(128); - errmsgs.append('\n').append(tmp.substring(1)); + errmsgs.append('\n').append(getLine().substring(1)); } } if (errmsgs == null) return null; return errmsgs.toString().trim(); } + + private final StringBuilder makeErrorBuffer() { + return new StringBuilder(128); + } + + public void close() throws IOException { + inner.close(); + } }
--- a/src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java +++ b/src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java @@ -98,6 +98,6 @@ public final class BufferedMCLWriter ext // reset reader state, last line isn't valid any more now if (reader != null) - reader.setLineType(null); + reader.resetLineType(); } }
--- a/src/main/java/org/monetdb/mcl/net/MapiSocket.java +++ b/src/main/java/org/monetdb/mcl/net/MapiSocket.java @@ -287,30 +287,30 @@ public class MapiSocket { /* cannot (yet } } - final String c = reader.readLine(); - reader.waitForPrompt(); + reader.advance(); + final String c = reader.getLine(); + reader.discardRemainder(); writer.writeLine(getChallengeResponse(c, user, pass, language, database, hash)); // read monetdb mserver response till prompt final ArrayList<String> redirects = new ArrayList<String>(); final List<String> warns = new ArrayList<String>(); String err = "", tmp; - LineType lineType; do { - tmp = reader.readLine(); + reader.advance(); + tmp = reader.getLine(); if (tmp == null) throw new IOException("Read from " + con.getInetAddress().getHostName() + ":" + con.getPort() + ": End of stream reached"); - lineType = reader.getLineType(); - if (lineType == LineType.ERROR) { + if (reader.getLineType() == LineType.ERROR) { err += "\n" + tmp.substring(7); - } else if (lineType == LineType.INFO) { + } else if (reader.getLineType() == LineType.INFO) { warns.add(tmp.substring(1)); - } else if (lineType == LineType.REDIRECT) { + } else if (reader.getLineType() == LineType.REDIRECT) { redirects.add(tmp.substring(1)); } - } while (lineType != LineType.PROMPT); + } while (reader.getLineType() != LineType.PROMPT); if (err.length() > 0) { close();
--- a/src/main/java/org/monetdb/merovingian/Control.java +++ b/src/main/java/org/monetdb/merovingian/Control.java @@ -218,9 +218,9 @@ public class Control { mout.writeLine(database + " " + command + "\n"); ArrayList<String> l = new ArrayList<String>(); - String tmpLine = min.readLine(); - LineType linetype = min.getLineType(); - switch (linetype) { + min.advance(); + String tmpLine = min.getLine(); + switch (min.getLineType()) { case ERROR: throw new MerovingianException(tmpLine.substring(6)); case RESULT: @@ -231,18 +231,16 @@ public class Control { throw new MerovingianException("unexpected line: " + tmpLine); } - boolean hasPrompt = false; - while (!hasPrompt) { - tmpLine = min.readLine(); - linetype = min.getLineType(); - switch (linetype) { - case PROMPT: - hasPrompt = true; - break; + lineloop: + while (true) { + min.advance(); + switch (min.getLineType()) { + case PROMPT: + break lineloop; case RESULT: l.add(tmpLine.substring(1)); - break; + continue lineloop; default: throw new MerovingianException("unexpected line: " + tmpLine); }
--- a/src/main/java/org/monetdb/util/SQLRestore.java +++ b/src/main/java/org/monetdb/util/SQLRestore.java @@ -51,7 +51,8 @@ public final class SQLRestore { public void run() { try { while (true) { - final String line = _is.readLine(); + _is.advance(); + final String line = _is.getLine(); if (line == null) break; final LineType result = _is.getLineType();
--- a/tests/JDBC_API_Tester.java +++ b/tests/JDBC_API_Tester.java @@ -6172,7 +6172,7 @@ final public class JDBC_API_Tester { org.monetdb.mcl.io.BufferedMCLReader mclIn = server.getReader(); org.monetdb.mcl.io.BufferedMCLWriter mclOut = server.getWriter(); - String error = mclIn.waitForPrompt(); + String error = mclIn.discardRemainder(); if (error != null) sb.append("Received start error: ").append(error).append("\n"); @@ -6190,13 +6190,13 @@ final public class JDBC_API_Tester { } mclOut.writeLine(""); // need this one for synchronisation over flush() - error = mclIn.waitForPrompt(); + error = mclIn.discardRemainder(); if (error != null) sb.append("Received error: ").append(error).append("\n"); mclOut.writeLine(""); // need this one for synchronisation over flush() - error = mclIn.waitForPrompt(); + error = mclIn.discardRemainder(); if (error != null) sb.append("Received finish error: ").append(error).append("\n");
--- a/tests/SQLcopyinto.java +++ b/tests/SQLcopyinto.java @@ -7,7 +7,6 @@ */ import java.sql.*; -import java.io.*; import java.util.*; import org.monetdb.mcl.net.MapiSocket; import org.monetdb.mcl.io.BufferedMCLReader; @@ -102,7 +101,7 @@ public class SQLcopyinto { BufferedMCLReader mclIn = server.getReader(); BufferedMCLWriter mclOut = server.getWriter(); - String error = mclIn.waitForPrompt(); + String error = mclIn.discardRemainder(); if (error != null) System.err.println("Received start error: " + error); @@ -120,12 +119,12 @@ public class SQLcopyinto { } mclOut.writeLine(""); // need this one for synchronisation over flush() - error = mclIn.waitForPrompt(); + error = mclIn.discardRemainder(); if (error != null) System.err.println("Received error: " + error); mclOut.writeLine(""); // need this one for synchronisation over flush() - error = mclIn.waitForPrompt(); + error = mclIn.discardRemainder(); if (error != null) System.err.println("Received finish error: " + error);