Mercurial > hg > monetdb-java
changeset 74:17365ed26611 embedded
In Java, you cannot get a pointer to a String, in order to make a faster memory copy. So I had to change a StringBuilder to a CharBuffer which allows to retrieve the pointer and make a faster processing.
line wrap: on
line diff
--- a/src/main/java/nl/cwi/monetdb/client/JMonetDB.java +++ b/src/main/java/nl/cwi/monetdb/client/JMonetDB.java @@ -119,7 +119,7 @@ copts.produceHelpMessage() } String[] commands = copts.getOption("command").getArguments(); - if (commands[0].equals("status")) { + /*if (commands[0].equals("status")) { List<SabaothDB> sdbs; if (commands.length == 1) { sdbs = ctl.getAllStatuses(); @@ -131,6 +131,6 @@ copts.produceHelpMessage() for (SabaothDB sdb : sdbs) { System.out.println(sdb.getName() + " " + sdb.getURI()); } - } + }*/ } }
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java @@ -3,6 +3,8 @@ package nl.cwi.monetdb.jdbc; import nl.cwi.monetdb.jdbc.types.INET; import nl.cwi.monetdb.jdbc.types.URL; import nl.cwi.monetdb.mcl.connection.*; +import nl.cwi.monetdb.mcl.connection.helpers.Debugger; +import nl.cwi.monetdb.mcl.connection.SenderThread; import nl.cwi.monetdb.mcl.connection.mapi.MapiLanguage; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.protocol.AbstractProtocol; @@ -66,7 +68,7 @@ public abstract class MonetConnection ex /** Authentication hash method */ protected final String hash; /** An optional thread that is used for sending large queries */ - private SendThread sendThread; + private SenderThread senderThread; /** Whether this Connection is closed (and cannot be used anymore) */ private boolean closed; /** Whether this Connection is in autocommit mode */ @@ -96,7 +98,7 @@ public abstract class MonetConnection ex private Debugger ourSavior; - protected AbstractProtocol<?> protocol; + protected AbstractProtocol protocol; /** * Constructor of a Connection for MonetDB. At this moment the current implementation limits itself to storing the @@ -156,7 +158,7 @@ public abstract class MonetConnection ex public abstract String getJDBCURL(); - public AbstractProtocol<?> getProtocol() { + public AbstractProtocol getProtocol() { return this.protocol; } @@ -170,36 +172,34 @@ public abstract class MonetConnection ex */ @Override public void close() { - synchronized(protocol) { - for (Statement st : statements.keySet()) { - try { - st.close(); - } catch (SQLException e) { - // better luck next time! - } - } - //close the debugger + for (Statement st : statements.keySet()) { try { - if (ourSavior != null) { - ourSavior.close(); - } - } catch (IOException e) { - // ignore it + st.close(); + } catch (SQLException e) { + // better luck next time! + } + } + //close the debugger + try { + if (ourSavior != null) { + ourSavior.close(); } - // close the socket or the embedded server - try { - this.closeUnderlyingConnection(); - } catch (IOException e) { - // ignore it - } - // close active SendThread if any - if (sendThread != null) { - sendThread.shutdown(); - sendThread = null; - } - // report ourselves as closed - closed = true; + } catch (IOException e) { + // ignore it } + // close the socket or the embedded server + try { + this.closeUnderlyingConnection(); + } catch (IOException e) { + // ignore it + } + // close active SendThread if any + if (senderThread != null) { + senderThread.shutdown(); + senderThread = null; + } + // report ourselves as closed + closed = true; } /** @@ -1310,20 +1310,18 @@ public abstract class MonetConnection ex * @throws SQLException if an IO exception or a database error occurs */ public void sendIndependentCommand(String command) throws SQLException { - synchronized (protocol) { - try { - protocol.writeNextQuery(language.getQueryTemplateIndex(0), command, language.getQueryTemplateIndex(1)); - protocol.waitUntilPrompt(); - if (protocol.getCurrentServerResponseHeader() == ServerResponses.ERROR) { - String error = protocol.getRemainingStringLine(0); - throw new SQLException(error.substring(6), error.substring(0, 5)); - } - } catch (SocketTimeoutException e) { - close(); // JDBC 4.1 semantics: abort() - throw new SQLException("connection timed out", "08M33"); - } catch (IOException e) { - throw new SQLException(e.getMessage(), "08000"); + try { + protocol.writeNextQuery(language.getQueryTemplateIndex(0), command, language.getQueryTemplateIndex(1)); + protocol.waitUntilPrompt(); + if (protocol.getCurrentServerResponseHeader() == ServerResponses.ERROR) { + String error = protocol.getRemainingStringLine(0); + throw new SQLException(error.substring(6), error.substring(0, 5)); } + } catch (SocketTimeoutException e) { + close(); // JDBC 4.1 semantics: abort() + throw new SQLException("connection timed out", "08M33"); + } catch (IOException e) { + throw new SQLException(e.getMessage(), "08000"); } } @@ -1495,192 +1493,190 @@ public abstract class MonetConnection ex String error = null; try { - synchronized (protocol) { - // make sure we're ready to send query; read data till we 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. - protocol.waitUntilPrompt(); + // make sure we're ready to send query; read data till we 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. + protocol.waitUntilPrompt(); - // {{{ set reply size - /** - * Change the reply size of the server. If the given value is the same as the current value known - * to use, then ignore this call. If it is set to 0 we get a prompt after the server sent it's - * header. - */ - int size = cachesize == 0 ? DEF_FETCHSIZE : cachesize; - size = maxrows != 0 ? Math.min(maxrows, size) : size; - // don't do work if it's not needed - if (language == MapiLanguage.LANG_SQL && size != curReplySize && - !Arrays.deepEquals(templ, language.getCommandTemplates())) { - sendControlCommand(ControlCommands.REPLY_SIZE, size); + // {{{ set reply size + /** + * Change the reply size of the server. If the given value is the same as the current value known + * to use, then ignore this call. If it is set to 0 we get a prompt after the server sent it's + * header. + */ + int size = cachesize == 0 ? DEF_FETCHSIZE : cachesize; + size = maxrows != 0 ? Math.min(maxrows, size) : size; + // don't do work if it's not needed + if (language == MapiLanguage.LANG_SQL && size != curReplySize && + !Arrays.deepEquals(templ, language.getCommandTemplates())) { + sendControlCommand(ControlCommands.REPLY_SIZE, size); - // store the reply size after a successful change - curReplySize = size; - } - // }}} set reply size + // store the reply size after a successful change + curReplySize = size; + } + // }}} set reply size - // If the query is larger than the TCP buffer size, use a special send thread to avoid deadlock with - // the server due to blocking behaviour when the buffer is full. Because the server will be writing - // back results to us, it will eventually block as well when its TCP buffer gets full, as we are - // blocking an not consuming from it. The result is a state where both client and server want to - // write, but block. - if (query.length() > getBlockSize()) { - // get a reference to the send thread - if (sendThread == null) { - sendThread = new SendThread(protocol); - } - // tell it to do some work! - sendThread.runQuery(templ, query); - } else { - // this is a simple call, which is a lot cheaper and will - // always succeed for small queries. - protocol.writeNextQuery((templ[0] == null) ? "" : templ[0], query, - (templ[1] == null) ? "" : templ[1]); + // If the query is larger than the TCP buffer size, use a special send thread to avoid deadlock with + // the server due to blocking behaviour when the buffer is full. Because the server will be writing + // back results to us, it will eventually block as well when its TCP buffer gets full, as we are + // blocking an not consuming from it. The result is a state where both client and server want to + // write, but block. + if (query.length() > getBlockSize()) { + // get a reference to the send thread + if (senderThread == null) { + senderThread = new SenderThread(protocol); } + // tell it to do some work! + senderThread.runQuery(templ, query); + } else { + // this is a simple call, which is a lot cheaper and will + // always succeed for small queries. + protocol.writeNextQuery((templ[0] == null) ? "" : templ[0], query, + (templ[1] == null) ? "" : templ[1]); + } - // go for new results - protocol.fetchNextResponseData(); - ServerResponses nextResponse = protocol.getCurrentServerResponseHeader(); - IResponse res = null; - while (nextResponse != ServerResponses.PROMPT) { - // each response should start with a start of header (or error) - switch (nextResponse) { - case SOHEADER: - // make the response object, and fill it - try { - switch (protocol.getNextStarterHeader()) { - case Q_PARSE: - throw new ProtocolException("Q_PARSE header not allowed here", 1); - case Q_TABLE: - case Q_PREPARE: { - res = protocol.getNextResultSetResponse(MonetConnection.this, - ResponseList.this, this.seqnr); - ResultSetResponse rsreponse = (ResultSetResponse) res; - // only add this resultset to the hashmap if it can possibly - // have an additional datablock - if (rsreponse.getRowcount() < rsreponse.getTuplecount()) { - if (rsresponses == null) { - rsresponses = new HashMap<>(); - } - rsresponses.put(rsreponse.getId(), rsreponse); + // go for new results + protocol.fetchNextResponseData(); + ServerResponses nextResponse = protocol.getCurrentServerResponseHeader(); + IResponse res = null; + while (nextResponse != ServerResponses.PROMPT) { + // each response should start with a start of header (or error) + switch (nextResponse) { + case SOHEADER: + // make the response object, and fill it + try { + switch (protocol.getNextStarterHeader()) { + case Q_PARSE: + throw new ProtocolException("Q_PARSE header not allowed here", 1); + case Q_TABLE: + case Q_PREPARE: { + res = protocol.getNextResultSetResponse(MonetConnection.this, + ResponseList.this, this.seqnr); + ResultSetResponse rsreponse = (ResultSetResponse) res; + // only add this resultset to the hashmap if it can possibly + // have an additional datablock + if (rsreponse.getRowcount() < rsreponse.getTuplecount()) { + if (rsresponses == null) { + rsresponses = new HashMap<>(); } + rsresponses.put(rsreponse.getId(), rsreponse); } + } + break; + case Q_UPDATE: + res = protocol.getNextUpdateResponse(); + break; + case Q_SCHEMA: + res = protocol.getNextSchemaResponse(); break; - case Q_UPDATE: - res = protocol.getNextUpdateResponse(); + case Q_TRANS: + res = protocol.getNextAutoCommitResponse(); + boolean isAutoCommit = ((AutoCommitResponse) res).isAutocommit(); + + if (MonetConnection.this.getAutoCommit() && isAutoCommit) { + MonetConnection.this.addWarning("Server enabled auto commit mode " + + "while local state already was auto commit.", "01M11"); + } + MonetConnection.this.autoCommit = isAutoCommit; + break; + case Q_BLOCK: { + DataBlockResponse next = protocol.getNextDatablockResponse(rsresponses); + if (next == null) { + error = "M0M12!No ResultSetResponse for a DataBlock found"; break; - case Q_SCHEMA: - res = protocol.getNextSchemaResponse(); - break; - case Q_TRANS: - res = protocol.getNextAutoCommitResponse(); - boolean isAutoCommit = ((AutoCommitResponse) res).isAutocommit(); - - if (MonetConnection.this.getAutoCommit() && isAutoCommit) { - MonetConnection.this.addWarning("Server enabled auto commit mode " + - "while local state already was auto commit.", "01M11"); - } - MonetConnection.this.autoCommit = isAutoCommit; - break; - case Q_BLOCK: { - DataBlockResponse next = protocol.getNextDatablockResponse(rsresponses); - if (next == null) { - error = "M0M12!No ResultSetResponse for a DataBlock found"; - break; - } - res = next; } - break; + res = next; } - } catch (ProtocolException e) { - error = "M0M10!error while parsing start of header:\n" + e.getMessage() + " found: '" - + protocol.getRemainingStringLine(0).charAt(e.getErrorOffset()) + "'" + - " in: \"" + protocol.getRemainingStringLine(0) + "\"" + " at pos: " - + e.getErrorOffset(); - // flush all the rest - protocol.waitUntilPrompt(); - nextResponse = protocol.getCurrentServerResponseHeader(); break; } - - // immediately handle errors after parsing the header (res may be null) - if (error != null) { - protocol.waitUntilPrompt(); - nextResponse = protocol.getCurrentServerResponseHeader(); - break; - } - - // here we have a res object, which we can start filling - if (res instanceof IIncompleteResponse) { - IIncompleteResponse iter = (IIncompleteResponse) res; - while (iter.wantsMore()) { - try { - protocol.fetchNextResponseData(); - iter.addLine(protocol.getCurrentServerResponseHeader(), protocol.getCurrentData()); - } catch (ProtocolException ex) { - // right, some protocol violation, skip the rest of the result - error = "M0M10!" + ex.getMessage(); - protocol.waitUntilPrompt(); - nextResponse = protocol.getCurrentServerResponseHeader(); - break; - } - } - } - - if (error != null) { - break; - } - - // it is of no use to store DataBlockResponses, you never want to - // retrieve them directly anyway - if (!(res instanceof DataBlockResponse)) { - responses.add(res); - } - // read the next line (can be prompt, new result, error, etc.) before we start the loop over - protocol.fetchNextResponseData(); - nextResponse = protocol.getCurrentServerResponseHeader(); - break; - case INFO: - addWarning(protocol.getRemainingStringLine(1), "01000"); - // read the next line (can be prompt, new result, error, etc.) before we start the loop over - protocol.fetchNextResponseData(); - nextResponse = protocol.getCurrentServerResponseHeader(); - 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 = protocol.getRemainingStringLine(1); + } catch (ProtocolException e) { + error = "M0M10!error while parsing start of header:\n" + e.getMessage() + " found: '" + + protocol.getRemainingStringLine(0).charAt(e.getErrorOffset()) + "'" + + " in: \"" + protocol.getRemainingStringLine(0) + "\"" + " at pos: " + + e.getErrorOffset(); + // flush all the rest protocol.waitUntilPrompt(); nextResponse = protocol.getCurrentServerResponseHeader(); break; - default: - throw new SQLException("Protocol violation, unexpected line!", "M0M10"); + } + + // immediately handle errors after parsing the header (res may be null) + if (error != null) { + protocol.waitUntilPrompt(); + nextResponse = protocol.getCurrentServerResponseHeader(); + break; + } + + // here we have a res object, which we can start filling + if (res instanceof IIncompleteResponse) { + IIncompleteResponse iter = (IIncompleteResponse) res; + while (iter.wantsMore()) { + try { + protocol.fetchNextResponseData(); + iter.addLine(protocol.getCurrentServerResponseHeader(), protocol.getCurrentData()); + } catch (ProtocolException ex) { + // right, some protocol violation, skip the rest of the result + error = "M0M10!" + ex.getMessage(); + protocol.waitUntilPrompt(); + nextResponse = protocol.getCurrentServerResponseHeader(); + break; + } + } + } + + if (error != null) { + break; + } + + // it is of no use to store DataBlockResponses, you never want to + // retrieve them directly anyway + if (!(res instanceof DataBlockResponse)) { + responses.add(res); + } + // read the next line (can be prompt, new result, error, etc.) before we start the loop over + protocol.fetchNextResponseData(); + nextResponse = protocol.getCurrentServerResponseHeader(); + break; + case INFO: + addWarning(protocol.getRemainingStringLine(0), "01000"); + // read the next line (can be prompt, new result, error, etc.) before we start the loop over + protocol.fetchNextResponseData(); + nextResponse = protocol.getCurrentServerResponseHeader(); + 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 = protocol.getRemainingStringLine(0); + protocol.waitUntilPrompt(); + nextResponse = protocol.getCurrentServerResponseHeader(); + break; + default: + throw new SQLException("Protocol violation, unexpected line!", "M0M10"); + } + } + + // if we used the senderThread, make sure it has finished + if (senderThread != null) { + String tmp = senderThread.getErrors(); + if (tmp != null) { + if (error == null) { + error = "08000!" + tmp; + } else { + error += "\n08000!" + tmp; } } - - // if we used the sendThread, make sure it has finished - if (sendThread != null) { - String tmp = sendThread.getErrors(); - if (tmp != null) { - if (error == null) { - error = "08000!" + tmp; - } else { - error += "\n08000!" + tmp; - } + } + if (error != null) { + SQLException ret = null; + String[] errors = error.split("\n"); + for (String error1 : errors) { + if (ret == null) { + ret = new SQLException(error1.substring(6), error1.substring(0, 5)); + } else { + ret.setNextException(new SQLException(error1.substring(6), error1.substring(0, 5))); } } - if (error != null) { - SQLException ret = null; - String[] errors = error.split("\n"); - for (String error1 : errors) { - if (ret == null) { - ret = new SQLException(error1.substring(6), error1.substring(0, 5)); - } else { - ret.setNextException(new SQLException(error1.substring(6), error1.substring(0, 5))); - } - } - throw ret; - } + throw ret; } } catch (SocketTimeoutException e) { this.close(); // JDBC 4.1 semantics, abort()
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetSavepoint.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetSavepoint.java @@ -53,8 +53,7 @@ public class MonetSavepoint implements S } /** - * Retrieves the generated ID for the savepoint that this Savepoint object - * represents. + * Retrieves the generated ID for the savepoint that this Savepoint object represents. * * @return the numeric ID of this savepoint * @throws SQLException if this is a named savepoint @@ -68,8 +67,7 @@ public class MonetSavepoint implements S } /** - * Retrieves the name of the savepoint that this Savepoint object - * represents. + * Retrieves the name of the savepoint that this Savepoint object represents. * * @return the name of this savepoint * @throws SQLException if this is an un-named savepoint @@ -85,9 +83,8 @@ public class MonetSavepoint implements S //== end of methods from Savepoint interface /** - * Retrieves the savepoint id, like the getSavepointId method with the only - * difference that this method will always return the id, regardless of - * whether it is named or not. + * Retrieves the savepoint id, like the getSavepointId method with the only difference that this method will always + * return the id, regardless of whether it is named or not. * * @return the numeric ID of this savepoint */ @@ -96,9 +93,8 @@ public class MonetSavepoint implements S } /** - * Returns the name to use when referencing this save point to the MonetDB - * database. The returned value is guaranteed to be unique and consists of - * a prefix string and a sequence number. + * Returns the name to use when referencing this save point to the MonetDB database. The returned value is + * guaranteed to be unique and consists of a prefix string and a sequence number. * * @return the unique savepoint name */ @@ -114,8 +110,7 @@ public class MonetSavepoint implements S * Therefore two successive calls to this method need not to have a * difference of 1. * - * @return the next int which is guaranteed to be higher than the one - * at the last call to this method. + * @return the next int which is guaranteed to be higher than the one at the last call to this method. */ private static int getNextId() { return highestId.incrementAndGet();
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java @@ -120,9 +120,8 @@ public class MonetStatement extends Mone //== methods of interface Statement /** - * Adds the given SQL command to the current list of commmands for this - * Statement object. The commands in this list can be executed as a - * batch by calling the method executeBatch. + * Adds the given SQL command to the current list of commands for this Statement object. The commands in this list + * can be executed as a batch by calling the method executeBatch. * * @param sql typically this is a static SQL INSERT or UPDATE statement * @throws SQLException so the PreparedStatement can throw this exception @@ -183,9 +182,6 @@ public class MonetStatement extends Mone */ @Override public int[] executeBatch() throws SQLException { - // this method is synchronized to make sure none gets in between the - // operations we execute below - batchLock.lock(); try { // don't think long if there isn't much to do
rename from src/main/java/nl/cwi/monetdb/mcl/connection/SendThread.java rename to src/main/java/nl/cwi/monetdb/mcl/connection/SenderThread.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/SendThread.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/SenderThread.java @@ -19,7 +19,7 @@ import java.util.concurrent.locks.Reentr * This thread is designed for reuse, as thread creation costs are * high. */ -public class SendThread extends Thread { +public class SenderThread extends Thread { private enum SendThreadStatus { /** The state WAIT represents this thread to be waiting for something to do */ @@ -44,7 +44,7 @@ public class SendThread extends Thread { * * @param out the socket to write to */ - public SendThread(AbstractProtocol out) { + public SenderThread(AbstractProtocol out) { super("SendThread"); this.setDaemon(true); this.protocol = out;
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/embedded/EmbeddedConnection.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/embedded/EmbeddedConnection.java @@ -79,8 +79,5 @@ public final class EmbeddedConnection ex @Override public void sendControlCommand(ControlCommands con, int data) throws SQLException { - synchronized (protocol) { - - } } }
new file mode 100644 --- /dev/null +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/BufferReallocator.java @@ -0,0 +1,36 @@ +package nl.cwi.monetdb.mcl.connection.helpers; + +import java.nio.CharBuffer; + +/** + * Created by ferreira on 12/14/16. + */ +public class BufferReallocator { + + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + private static int GetNewCapacity(CharBuffer oldBuffer) { + int minCapacity = oldBuffer.capacity() << 1; + int newCapacity = (oldBuffer.capacity() << 1) + 2; + if (newCapacity - minCapacity < 0) { + newCapacity = minCapacity; + } + + if(newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) { + if (Integer.MAX_VALUE - minCapacity < 0) { // overflow + throw new OutOfMemoryError(); + } + return (minCapacity > MAX_ARRAY_SIZE) ? minCapacity : MAX_ARRAY_SIZE; + } else { + return newCapacity; + } + } + + public static CharBuffer ReallocateBuffer(CharBuffer oldBuffer) { + int newCapacity = GetNewCapacity(oldBuffer); + CharBuffer newBuffer = CharBuffer.allocate(newCapacity); + oldBuffer.flip(); + newBuffer.put(oldBuffer.array()); + return newBuffer; + } +}
rename from src/main/java/nl/cwi/monetdb/mcl/connection/ChannelSecurity.java rename to src/main/java/nl/cwi/monetdb/mcl/connection/helpers/ChannelSecurity.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/ChannelSecurity.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/ChannelSecurity.java @@ -1,4 +1,4 @@ -package nl.cwi.monetdb.mcl.connection; +package nl.cwi.monetdb.mcl.connection.helpers; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException;
rename from src/main/java/nl/cwi/monetdb/mcl/connection/Debugger.java rename to src/main/java/nl/cwi/monetdb/mcl/connection/helpers/Debugger.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/Debugger.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/Debugger.java @@ -1,4 +1,4 @@ -package nl.cwi.monetdb.mcl.connection; +package nl.cwi.monetdb.mcl.connection.helpers; import java.io.*;
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java @@ -1,5 +1,7 @@ package nl.cwi.monetdb.mcl.connection.mapi; +import nl.cwi.monetdb.mcl.connection.helpers.BufferReallocator; + import java.io.Closeable; import java.io.IOException; import java.net.Socket; @@ -32,9 +34,7 @@ public abstract class AbstractSocket imp private final CharsetDecoder asciiDecoder = StandardCharsets.UTF_8.newDecoder(); - private boolean hasFinished; - - public AbstractSocket(String hostname, int port, MapiConnection connection) throws IOException { + AbstractSocket(String hostname, int port, MapiConnection connection) throws IOException { this.socket = new Socket(hostname, port); this.connection = connection; this.bufferIn = ByteBuffer.wrap(new byte[getBlockSize()]); @@ -44,19 +44,19 @@ public abstract class AbstractSocket imp this.stringsDecoded.flip(); } - public int getSoTimeout() throws SocketException { + int getSoTimeout() throws SocketException { return socket.getSoTimeout(); } - public void setSoTimeout(int s) throws SocketException { + void setSoTimeout(int s) throws SocketException { socket.setSoTimeout(s); } - public void setTcpNoDelay(boolean on) throws SocketException { + void setTcpNoDelay(boolean on) throws SocketException { socket.setTcpNoDelay(on); } - public void setSocketChannelEndianness(ByteOrder bo) { + void setSocketChannelEndianness(ByteOrder bo) { this.bufferIn.order(bo); this.bufferOut.order(bo); } @@ -72,8 +72,7 @@ public abstract class AbstractSocket imp private void readToBuffer() throws IOException { int read = this.readToBufferIn(this.bufferIn); if(read == 0) { - this.hasFinished = true; - throw new IOException("Done!"); + throw new IOException("The server has reached EOF!"); } this.stringsDecoded.clear(); this.asciiDecoder.reset(); @@ -82,27 +81,37 @@ public abstract class AbstractSocket imp this.stringsDecoded.flip(); } - public int readLine(StringBuilder builder) throws IOException { - builder.setLength(0); + public CharBuffer readLine(CharBuffer lineBuffer) throws IOException { + lineBuffer.clear(); boolean found = false; - char[] array = this.stringsDecoded.array(); - int position = this.stringsDecoded.position(); + char[] sourceArray = this.stringsDecoded.array(); + int sourcePosition = this.stringsDecoded.position(); + char[] destinationArray = lineBuffer.array(); + int destinationPosition = 0; + int destinationLimit = lineBuffer.limit(); while(!found) { if(!this.stringsDecoded.hasRemaining()) { this.readToBuffer(); - array = this.stringsDecoded.array(); - position = 0; + sourceArray = this.stringsDecoded.array(); + sourcePosition = 0; } - char c = array[position++]; + char c = sourceArray[sourcePosition++]; if(c == '\n') { found = true; } else { - builder.append(c); + if(destinationPosition + 1 >= destinationLimit) { + lineBuffer = BufferReallocator.ReallocateBuffer(lineBuffer); + destinationArray = lineBuffer.array(); + destinationLimit = lineBuffer.limit(); + } + destinationArray[destinationPosition++] = c; } } - this.stringsDecoded.position(position); - return builder.length(); + this.stringsDecoded.position(sourcePosition); + lineBuffer.position(destinationPosition); + lineBuffer.flip(); + return lineBuffer; } private void flushOutputCharBuffer() throws IOException { @@ -113,8 +122,7 @@ public abstract class AbstractSocket imp this.stringsEncoded.clear(); int written = this.writeFromBufferOut(this.bufferOut); if(written == 0) { - this.hasFinished = true; - throw new IOException("Done!"); + throw new IOException("The query could not be sent to the server!"); } else { this.flush(); } @@ -122,12 +130,18 @@ public abstract class AbstractSocket imp private void writeNextBlock(String line) throws IOException { int limit = line.length(); + int destinationPosition = this.stringsEncoded.position(); + char[] destinationArray = this.stringsEncoded.array(); + for (int i = 0; i < limit; i++) { if (!this.stringsEncoded.hasRemaining()) { this.flushOutputCharBuffer(); + destinationArray = this.stringsEncoded.array(); + destinationPosition = 0; } - this.stringsEncoded.put(line.charAt(i)); + destinationArray[destinationPosition++] = line.charAt(i); } + this.stringsEncoded.position(destinationPosition); } public void writeNextLine(String prefix, String line, String suffix) throws IOException {
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java @@ -1,7 +1,7 @@ package nl.cwi.monetdb.mcl.connection.mapi; import nl.cwi.monetdb.jdbc.MonetConnection; -import nl.cwi.monetdb.mcl.connection.ChannelSecurity; +import nl.cwi.monetdb.mcl.connection.helpers.ChannelSecurity; import nl.cwi.monetdb.mcl.connection.ControlCommands; import nl.cwi.monetdb.mcl.connection.MCLException; import nl.cwi.monetdb.mcl.protocol.ProtocolException; @@ -194,20 +194,18 @@ public class MapiConnection extends Mone case CLOSE: command = "close " + data; } - synchronized (protocol) { - try { - protocol.writeNextQuery(language.getCommandTemplateIndex(0), command, language.getCommandTemplateIndex(1)); - protocol.waitUntilPrompt(); - if (protocol.getCurrentServerResponseHeader() == ServerResponses.ERROR) { - String error = protocol.getRemainingStringLine(0); - throw new SQLException(error.substring(6), error.substring(0, 5)); - } - } catch (SocketTimeoutException e) { - close(); // JDBC 4.1 semantics, abort() - throw new SQLException("connection timed out", "08M33"); - } catch (IOException e) { - throw new SQLException(e.getMessage(), "08000"); + try { + protocol.writeNextQuery(language.getCommandTemplateIndex(0), command, language.getCommandTemplateIndex(1)); + protocol.waitUntilPrompt(); + if (protocol.getCurrentServerResponseHeader() == ServerResponses.ERROR) { + String error = protocol.getRemainingStringLine(0); + throw new SQLException(error.substring(6), error.substring(0, 5)); } + } catch (SocketTimeoutException e) { + close(); // JDBC 4.1 semantics, abort() + throw new SQLException("connection timed out", "08M33"); + } catch (IOException e) { + throw new SQLException(e.getMessage(), "08000"); } } @@ -233,7 +231,7 @@ public class MapiConnection extends Mone } this.protocol.fetchNextResponseData(); - String nextLine = this.protocol.getCurrentData().toString(); + String nextLine = this.protocol.getRemainingStringLine(0); this.protocol.waitUntilPrompt(); String test = this.getChallengeResponse(nextLine, user, pass, this.language.getRepresentation(), this.database, this.hash);
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/OldMapiSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/OldMapiSocket.java @@ -11,7 +11,7 @@ import java.nio.ByteBuffer; public class OldMapiSocket extends AbstractSocket { /** The blocksize (hardcoded in compliance with stream.mx) */ - private final static int BLOCK = 8 * 1024 - 2; + public final static int BLOCK = 8 * 1024 - 2; /** * A short in two bytes for holding the block size in bytes
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java @@ -13,7 +13,7 @@ import java.util.Map; /** * Created by ferreira on 11/30/16. */ -public abstract class AbstractProtocol<T> { +public abstract class AbstractProtocol { protected ServerResponses currentServerResponseHeader = ServerResponses.UNKNOWN; @@ -25,7 +25,7 @@ public abstract class AbstractProtocol<T return currentServerResponseHeader; } - public abstract T getCurrentData(); + public abstract Object getCurrentData(); public abstract StarterHeaders getNextStarterHeader();
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/embedded/EmbeddedProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/embedded/EmbeddedProtocol.java @@ -14,7 +14,7 @@ import java.util.Map; /** * Created by ferreira on 11/30/16. */ -public class EmbeddedProtocol extends AbstractProtocol<Object[]> { +public class EmbeddedProtocol extends AbstractProtocol { private final JDBCEmbeddedConnection connection; @@ -37,7 +37,7 @@ public class EmbeddedProtocol extends Ab } @Override - public Object[] getCurrentData() { + public Object getCurrentData() { return new Object[0]; }
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java @@ -13,7 +13,7 @@ import java.util.Map; /** * Created by ferreira on 11/30/16. */ -public class NewMapiProtocol extends AbstractProtocol<Object[]> { +public class NewMapiProtocol extends AbstractProtocol { @Override public ServerResponses waitUntilPrompt() throws IOException { @@ -26,7 +26,7 @@ public class NewMapiProtocol extends Abs } @Override - public Object[] getCurrentData() { + public Object getCurrentData() { return new Object[0]; }
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java @@ -13,47 +13,41 @@ import nl.cwi.monetdb.mcl.responses.Data import nl.cwi.monetdb.mcl.responses.ResultSetResponse; import java.io.IOException; +import java.nio.CharBuffer; import java.util.Map; /** * Created by ferreira on 11/30/16. */ -public class OldMapiProtocol extends AbstractProtocol<StringBuilder> { - - private static final int STRING_BUILDER_INITIAL_SIZE = 128; +public class OldMapiProtocol extends AbstractProtocol { private final OldMapiSocket socket; - final StringBuilder builder; - - int currentPointer = 0; + CharBuffer lineBuffer; private final StringBuilder tupleLineBuilder; public OldMapiProtocol(OldMapiSocket socket) { this.socket = socket; - this.builder = new StringBuilder(STRING_BUILDER_INITIAL_SIZE); - this.tupleLineBuilder = new StringBuilder(STRING_BUILDER_INITIAL_SIZE); + this.lineBuffer = CharBuffer.wrap(new char[OldMapiSocket.BLOCK]); + this.tupleLineBuilder = new StringBuilder(OldMapiSocket.BLOCK); } public OldMapiSocket getSocket() { return socket; } - boolean hasRemaining() { - return this.currentPointer < this.builder.length(); - } - @Override public ServerResponses waitUntilPrompt() throws IOException { while(this.currentServerResponseHeader != ServerResponses.PROMPT) { - if(this.socket.readLine(this.builder) == 0) { + this.lineBuffer = this.socket.readLine(this.lineBuffer); + if(this.lineBuffer.limit() == 0) { throw new IOException("Connection to server lost!"); } - this.currentPointer = 0; this.currentServerResponseHeader = OldMapiServerResponseParser.ParseOldMapiServerResponse(this); + this.lineBuffer.position(0); if (this.currentServerResponseHeader == ServerResponses.ERROR) { - this.currentPointer = 1; + this.lineBuffer.position(1); } } return this.currentServerResponseHeader; @@ -61,25 +55,30 @@ public class OldMapiProtocol extends Abs @Override public void fetchNextResponseData() throws IOException { //readLine equivalent - this.socket.readLine(this.builder); - this.currentPointer = 0; + this.lineBuffer = this.socket.readLine(this.lineBuffer); + if(this.lineBuffer.limit() == 0) { + throw new IOException("Connection to server lost!"); + } this.currentServerResponseHeader = OldMapiServerResponseParser.ParseOldMapiServerResponse(this); - if (this.currentServerResponseHeader == ServerResponses.ERROR && !this.builder.toString().matches("^![0-9A-Z]{5}!.+")) { - //this.builder.deleteCharAt(0); - this.builder.insert(0, "!22000!"); + if (this.currentServerResponseHeader == ServerResponses.ERROR && !this.lineBuffer.toString().matches("^[0-9A-Z]{5}!.+")) { + CharBuffer newbuffer = CharBuffer.wrap(new char[this.lineBuffer.capacity() + 7]); + newbuffer.put("!22000!"); + newbuffer.put(this.lineBuffer.array()); + newbuffer.flip(); + this.lineBuffer = newbuffer; } - this.currentPointer = 1; + this.lineBuffer.position(1); } @Override - public StringBuilder getCurrentData() { - return this.builder; + public CharBuffer getCurrentData() { + return this.lineBuffer; } @Override public StarterHeaders getNextStarterHeader() { StarterHeaders res = OldMapiStartOfHeaderParser.GetNextStartHeaderOnOldMapi(this); - this.currentPointer += 2; + this.lineBuffer.position(this.lineBuffer.position() + 1); return res; } @@ -124,19 +123,19 @@ public class OldMapiProtocol extends Abs @Override public TableResultHeaders getNextTableHeader(Object line, String[] stringValues, int[] intValues) throws ProtocolException { - return OldMapiTableHeaderParser.GetNextTableHeader((StringBuilder) line, stringValues, intValues); + return OldMapiTableHeaderParser.GetNextTableHeader((CharBuffer) line, stringValues, intValues); } @Override public int parseTupleLine(int lineNumber, Object line, int[] typesMap, Object[] data, boolean[] nulls) throws ProtocolException { - return OldMapiTupleLineParser.OldMapiParseTupleLine(lineNumber, (StringBuilder) line, + return OldMapiTupleLineParser.OldMapiParseTupleLine(lineNumber, (CharBuffer) line, this.tupleLineBuilder, typesMap, data, nulls); } @Override public String getRemainingStringLine(int startIndex) { - return this.builder.substring(startIndex); + return new String(this.lineBuffer.array(), startIndex, this.lineBuffer.limit() - startIndex); } @Override
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiServerResponseParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiServerResponseParser.java @@ -9,7 +9,7 @@ final class OldMapiServerResponseParser static ServerResponses ParseOldMapiServerResponse(OldMapiProtocol protocol) { ServerResponses res; - switch (protocol.builder.charAt(protocol.currentPointer)) { + switch (protocol.lineBuffer.get()) { case '!': res = ServerResponses.ERROR; break;
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiStartOfHeaderParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiStartOfHeaderParser.java @@ -10,7 +10,7 @@ final class OldMapiStartOfHeaderParser { static StarterHeaders GetNextStartHeaderOnOldMapi(OldMapiProtocol protocol) { StarterHeaders res; - switch (protocol.builder.charAt(protocol.currentPointer)) { + switch (protocol.lineBuffer.get()) { case '0': res = StarterHeaders.Q_PARSE; break; @@ -39,23 +39,24 @@ final class OldMapiStartOfHeaderParser { } static int GetNextResponseDataAsInt(OldMapiProtocol protocol) throws ProtocolException { - if (!protocol.hasRemaining()) { - throw new ProtocolException("unexpected end of string", protocol.currentPointer - 1); + int currentPointer = protocol.lineBuffer.position(); + int limit = protocol.lineBuffer.limit(); + char[] array = protocol.lineBuffer.array(); + + if (currentPointer >= limit) { + throw new ProtocolException("unexpected end of string", currentPointer - 1); } int tmp; - char chr = protocol.builder.charAt(protocol.currentPointer); - protocol.currentPointer++; - // note: don't use Character.isDigit() here, because - // we only want ISO-LATIN-1 digits + char chr = array[currentPointer++]; + // note: don't use Character.isDigit() here, because we only want ISO-LATIN-1 digits if (chr >= '0' && chr <= '9') { tmp = (int)chr - (int)'0'; } else { - throw new ProtocolException("expected a digit", protocol.currentPointer - 1); + throw new ProtocolException("expected a digit", currentPointer - 1); } - while (protocol.hasRemaining()) { - chr = protocol.builder.charAt(protocol.currentPointer); - protocol.currentPointer++; + while (currentPointer < limit) { + chr = array[currentPointer++]; if(chr == ' ') { break; } @@ -63,29 +64,33 @@ final class OldMapiStartOfHeaderParser { if (chr >= '0' && chr <= '9') { tmp += (int)chr - (int)'0'; } else { - throw new ProtocolException("expected a digit", protocol.currentPointer - 1); + throw new ProtocolException("expected a digit", currentPointer - 1); } } + protocol.lineBuffer.position(currentPointer); return tmp; } static String GetNextResponseDataAsString(OldMapiProtocol protocol) throws ProtocolException { - if (!protocol.hasRemaining()) { - throw new ProtocolException("unexpected end of string", protocol.currentPointer - 1); + int currentPointer = protocol.lineBuffer.position(); + int limit = protocol.lineBuffer.limit(); + char[] array = protocol.lineBuffer.array(); + + if (currentPointer >= limit) { + throw new ProtocolException("unexpected end of string", currentPointer - 1); } - int cnt = 0, mark = protocol.currentPointer; + int cnt = 0, mark = currentPointer; char chr; - while (protocol.hasRemaining()) { - chr = protocol.builder.charAt(protocol.currentPointer); - protocol.currentPointer++; + while (currentPointer < limit) { + chr = array[currentPointer++]; if(chr == ' ') { break; } cnt++; } - protocol.currentPointer = mark; - return protocol.builder.subSequence(0, cnt).toString(); + protocol.lineBuffer.position(mark); + return new String(array, 0, cnt); } }
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java @@ -3,25 +3,31 @@ package nl.cwi.monetdb.mcl.protocol.oldm import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.protocol.TableResultHeaders; +import java.nio.CharBuffer; + /** * Created by ferreira on 12/6/16. */ final class OldMapiTableHeaderParser { - static TableResultHeaders GetNextTableHeader(StringBuilder builder, String[] stringValues, int[] intValues) throws ProtocolException { + static TableResultHeaders GetNextTableHeader(CharBuffer lineBuffer, String[] stringValues, int[] intValues) + throws ProtocolException { TableResultHeaders res = TableResultHeaders.UNKNOWN; - int len = builder.length(), pos = 0; + int currentLength = lineBuffer.limit(); + char[] array = lineBuffer.array(); + + int pos = 0; boolean foundChar = false, nameFound = false; // find header name - for (int i = len - 1; i >= 0; i--) { - switch (builder.charAt(i)) { + for (int i = currentLength - 1; i >= 0; i--) { + switch (array[i]) { case ' ': case '\n': case '\t': case '\r': if (!foundChar) { - len = i - 1; + currentLength = i - 1; } else { pos = i + 1; } @@ -42,62 +48,62 @@ final class OldMapiTableHeaderParser { throw new ProtocolException("invalid header, no header name found", pos); // depending on the name of the header, we continue - switch (builder.charAt(pos)) { + switch (array[pos]) { case 'n': //name - if (len - pos == 4) { - GetStringValues(builder, pos - 3, stringValues); + if (currentLength - pos == 4) { + GetStringValues(array, pos - 3, stringValues); res = TableResultHeaders.NAME; } break; case 'l': //length - if (len - pos == 6) { - GetIntValues(builder, pos - 3, intValues); + if (currentLength - pos == 6) { + GetIntValues(array, pos - 3, intValues); res = TableResultHeaders.LENGTH; } break; case 't': - if (len - pos == 4) { //type - GetStringValues(builder, pos - 3, stringValues); + if (currentLength - pos == 4) { //type + GetStringValues(array, pos - 3, stringValues); res = TableResultHeaders.TYPE; - } else if (len - pos == 10) { //table_name - GetStringValues(builder, pos - 3, stringValues); + } else if (currentLength - pos == 10) { //table_name + GetStringValues(array, pos - 3, stringValues); res = TableResultHeaders.TABLE; } break; default: - throw new ProtocolException("unknown header: " + builder.substring(pos, len)); + throw new ProtocolException("unknown header: " + new String(array, pos, currentLength - pos)); } return res; } - private static void GetStringValues(StringBuilder builder, int stop, String[] stringValues) { + private static void GetStringValues(char[] array, int stop, String[] stringValues) { int elem = 0, start = 2; for (int i = start + 1; i < stop; i++) { - if (builder.charAt(i) == '\t' && builder.charAt(i - 1) == ',') { - stringValues[elem++] = builder.substring(start, i - 1); + if (array[i] == '\t' && array[i - 1] == ',') { + stringValues[elem++] = new String(array, start, i - 1 - start); start = i + 1; } } // add the left over part - stringValues[elem] = builder.substring(start, stop); + stringValues[elem] = new String(array, start, stop - start); } - private static void GetIntValues(StringBuilder builder, int stop, int[] intValues) throws ProtocolException { + private static void GetIntValues(char[] array, int stop, int[] intValues) throws ProtocolException { int elem = 0, tmp = 0, start = 2; for (int i = start; i < stop; i++) { - if (builder.charAt(i) == ',' && builder.charAt(i + 1) == '\t') { + if (array[i] == ',' && array[i + 1] == '\t') { intValues[elem++] = tmp; tmp = 0; i++; } else { tmp *= 10; // note: don't use Character.isDigit() here, because we only want ISO-LATIN-1 digits - if (builder.charAt(i) >= '0' && builder.charAt(i) <= '9') { - tmp += (int) builder.charAt(i) - (int)'0'; + if (array[i] >= '0' && array[i] <= '9') { + tmp += (int) array[i] - (int)'0'; } else { - throw new ProtocolException("expected a digit in " + builder.toString() + " at " + i); + throw new ProtocolException("expected a digit in " + new String(array) + " at " + i); } } }
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java @@ -6,10 +6,9 @@ import nl.cwi.monetdb.mcl.protocol.Proto import java.math.BigDecimal; import java.math.BigInteger; -import java.sql.Date; +import java.nio.CharBuffer; import java.sql.Types; import java.text.ParseException; -import java.text.ParsePosition; import java.text.SimpleDateFormat; /** @@ -17,17 +16,49 @@ import java.text.SimpleDateFormat; */ final class OldMapiTupleLineParser { - static int OldMapiParseTupleLine(int lineNumber, StringBuilder line, StringBuilder helper, int[] typesMap, + private static final char[] NULL_STRING = "NULL".toCharArray(); + + private static int CharIndexOf(char[] source, int sourceCount, char[] target, int targetCount) { + if (targetCount == 0) { + return 0; + } + + char first = target[0]; + int max = sourceCount - targetCount; + + for (int i = 0; i <= max; i++) { + /* Look for first character. */ + if (source[i] != first) { + while (++i <= max && source[i] != first); + } + + /* Found first character, now look at the rest of v2 */ + if (i <= max) { + int j = i + 1; + int end = j + targetCount - 1; + for (int k = 1; j < end && source[j] == target[k]; j++, k++); + + if (j == end) { + /* Found whole string. */ + return i; + } + } + } + return -1; + } + + static int OldMapiParseTupleLine(int lineNumber, CharBuffer lineBuffer, StringBuilder helper, int[] typesMap, Object[] values, boolean[] nulls) throws ProtocolException { - int len = line.length(); + int len = lineBuffer.limit(); + char[] array = lineBuffer.array(); // first detect whether this is a single value line (=) or a real tuple ([) - if (line.charAt(0) == '=') { + if (array[0] == '=') { if (typesMap.length != 1) { throw new ProtocolException(typesMap.length + " columns expected, but only single value found"); } // return the whole string but the leading = - OldMapiStringToJavaObjectConverter(line.substring(1), lineNumber, values[0], typesMap[0]); + OldMapiStringToJavaObjectConverter(new String(array, 1, len - 1), lineNumber, values[0], typesMap[0]); return 1; } @@ -35,7 +66,7 @@ final class OldMapiTupleLineParser { boolean inString = false, escaped = false; int cursor = 2, column = 0, i = 2; for (; i < len; i++) { - switch(line.charAt(i)) { + switch(array[i]) { default: escaped = false; break; @@ -44,17 +75,13 @@ final class OldMapiTupleLineParser { break; case '"': /** - * If all strings are wrapped between two quotes, a \" can - * never exist outside a string. Thus if we believe that we - * are not within a string, we can safely assume we're about - * to enter a string if we find a quote. - * If we are in a string we should stop being in a string if - * we find a quote which is not prefixed by a \, for that - * would be an escaped quote. However, a nasty situation can - * occur where the string is like "test \\" as obvious, a - * test for a \ in front of a " doesn't hold here for all - * cases. Because "test \\\"" can exist as well, we need to - * know if a quote is prefixed by an escaping slash or not. + * If all strings are wrapped between two quotes, a \" can never exist outside a string. Thus if we + * believe that we are not within a string, we can safely assume we're about to enter a string if we + * find a quote. If we are in a string we should stop being in a string if we find a quote which is + * not prefixed by a \, for that would be an escaped quote. However, a nasty situation can occur + * where the string is like "test \\" as obvious, a test for a \ in front of a " doesn't hold here + * for all cases. Because "test \\\"" can exist as well, we need to know if a quote is prefixed by + * an escaping slash or not. */ if (!inString) { inString = true; @@ -66,19 +93,18 @@ final class OldMapiTupleLineParser { escaped = false; break; case '\t': - if (!inString && (i > 0 && line.charAt(i - 1) == ',') || (i + 1 == len - 1 && line.charAt(++i) == ']')) { // dirty + if (!inString && (i > 0 && array[i - 1] == ',') || (i + 1 == len - 1 && array[++i] == ']')) { // dirty // split! - if (line.charAt(cursor) == '"' && line.charAt(i - 2) == '"') { + if (array[cursor] == '"' && array[i - 2] == '"') { // reuse the StringBuilder by cleaning it helper.setLength(0); // prevent capacity increases helper.ensureCapacity((i - 2) - (cursor + 1)); for (int pos = cursor + 1; pos < i - 2; pos++) { - if (line.charAt(pos) == '\\' && pos + 1 < i - 2) { + if (array[cursor] == '\\' && pos + 1 < i - 2) { pos++; - // strToStr and strFromStr in gdk_atoms.mx only - // support \t \n \\ \" and \377 - switch (line.charAt(pos)) { + // strToStr and strFromStr in gdk_atoms.mx only support \t \n \\ \" and \377 + switch (array[pos]) { case '\\': helper.append('\\'); break; @@ -93,12 +119,11 @@ final class OldMapiTupleLineParser { break; case '0': case '1': case '2': case '3': // this could be an octal number, let's check it out - if (pos + 2 < i - 2 && - line.charAt(pos + 1) >= '0' && line.charAt(pos + 1) <= '7' && - line.charAt(pos + 2) >= '0' && line.charAt(pos + 2) <= '7') { + if (pos + 2 < i - 2 && array[pos + 1] >= '0' && array[pos + 1] <= '7' && + array[pos + 2] >= '0' && array[pos + 2] <= '7') { // we got the number! try { - helper.append((char)(Integer.parseInt("" + line.charAt(pos) + line.charAt(pos + 1) + line.charAt(pos + 2), 8))); + helper.append((char)(Integer.parseInt("" + array[pos] + array[pos + 1] + array[pos + 2], 8))); pos += 2; } catch (NumberFormatException e) { // hmmm, this point should never be reached actually... @@ -106,27 +131,26 @@ final class OldMapiTupleLineParser { } } else { // do default action if number seems not to be correct - helper.append(line.charAt(pos)); + helper.append(array[pos]); } break; default: // this is wrong, just ignore the escape, and print the char - helper.append(line.charAt(pos)); + helper.append(array[pos]); break; } } else { - helper.append(line.charAt(pos)); + helper.append(array[pos]); } } - // put the unescaped string in the right place OldMapiStringToJavaObjectConverter(helper.toString(), lineNumber, values[column], typesMap[column]); nulls[column] = false; - } else if ((i - 1) - cursor == 4 && line.indexOf("NULL", cursor) == cursor) { + } else if ((i - 1) - cursor == 4 && CharIndexOf(array, array.length, NULL_STRING, NULL_STRING.length) == cursor) { SetNullValue(lineNumber, values[column], typesMap[column]); nulls[column] = true; } else { - OldMapiStringToJavaObjectConverter(line.substring(cursor, i - 1), lineNumber, values[column], typesMap[column]); + OldMapiStringToJavaObjectConverter(new String(array, cursor, i - 1 - cursor), lineNumber, values[column], typesMap[column]); nulls[column] = false; } column++;
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java @@ -31,7 +31,7 @@ public class DataBlockResponse implement /** The counter which keeps the current position in the data array */ private int pos; /** The connection protocol to parse the tuple lines */ - private final AbstractProtocol<?> protocol; + private final AbstractProtocol protocol; /** The JdbcSQLTypes mapping */ private final int[] jdbcSQLTypes; /** A mapping of null values of the current Row */ @@ -46,7 +46,7 @@ public class DataBlockResponse implement * @param columncount the number of columns * @param forward whether this is a forward only result */ - DataBlockResponse(int rowcount, int columncount, boolean forward, AbstractProtocol<?> protocol, int[] JdbcSQLTypes) { + DataBlockResponse(int rowcount, int columncount, boolean forward, AbstractProtocol protocol, int[] JdbcSQLTypes) { this.pos = -1; this.data = new Object[columncount]; this.nullMappings = new boolean[rowcount][columncount]; @@ -154,7 +154,7 @@ public class DataBlockResponse implement } public Object[] getData() { /* For VirtualResultSet :( */ - return data; + return this.data; } public boolean checkValueIsNull(int column) {