Mercurial > hg > monetdb-java
changeset 99:1dcb51573c89 embedded
Added the remaining documentation.
line wrap: on
line diff
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java @@ -51,7 +51,7 @@ public abstract class MonetConnection ex return SeqCounter; } - /** the successful processed input properties */ + /** The successful processed input properties */ protected final Properties conn_props; /** The language to connect with */ protected IMonetDBLanguage language; @@ -110,8 +110,7 @@ public abstract class MonetConnection ex } /** - * Connects to the given host and port, logging in as the given user. If followRedirect is false, a - * RedirectionException is thrown when a redirect is encountered. + * Connects to the server, authenticating the user. * * @return A List with informational (warning) messages. If this list is empty; then there are no warnings. * @throws IOException if an I/O error occurs when creating the socket @@ -120,20 +119,67 @@ public abstract class MonetConnection ex */ public abstract List<String> connect(String user, String pass) throws IOException, ProtocolException, MCLException; + /** + * Gets the underlying connection block size length. + * + * @return The block size length + */ public abstract int getBlockSize(); + /** + * Gets the underlying connection default fetch size for DataBlock responses. + * + * @return The default fetch size + */ public abstract int getDefFetchsize(); + /** + * Gets the underlying connection socket timeout. + * + * @return The underlying connection socket timeout + */ public abstract int getSoTimeout(); - public abstract void setSoTimeout(int s); + /** + * Sets the underlying connection socket timeout. + * + * @param timeout The specified timeout, in milliseconds. A timeout of zero is interpreted as an infinite timeout + */ + public abstract void setSoTimeout(int timeout); + /** + * Closes the underlying connection implementation. + * + * @throws IOException if an I/O error occurs while closing the connection + */ public abstract void closeUnderlyingConnection() throws IOException; + /** + * Gets the underlying connection JDBC String URL. + * + * @return The underlying connection JDBC String URL + */ public abstract String getJDBCURL(); - public abstract void sendControlCommand(int con, int data) throws SQLException; + /** + * Sends a control command to the server. + * + * @param commandID the command identifier according to {@link ControlCommands} listing + * @param data The integer to send according to the control command + * @throws SQLException if an IO exception or a database error occurs + */ + public abstract void sendControlCommand(int commandID, int data) throws SQLException; + /** + * Creates a ResponseList. + * + * @param fetchSize the nubmer of rows per block in the response list + * @param maxRows maximum number of rows to allow in the set + * @param resultSetType the type of result sets to produce + * @param resultSetConcurrency the concurrency of result sets to produce + * @return A ResponseList instance + * @throws SQLException if an IO exception or a database error occurs + */ public abstract ResponseList createResponseList(int fetchSize, int maxRows, int resultSetType, int resultSetConcurrency) throws SQLException;
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/ControlCommands.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/ControlCommands.java @@ -10,6 +10,8 @@ package nl.cwi.monetdb.mcl.connection; /** * The listening of the MonetDB's control commands sent by the client during a JDBC connection. + * + * @author Fabian Groffen, Pedro Ferreira */ public final class ControlCommands {
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/IMonetDBLanguage.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/IMonetDBLanguage.java @@ -11,6 +11,8 @@ package nl.cwi.monetdb.mcl.connection; /** * An interface which represents the delimiters for user queries depending on the language (SQL and MAL) and connection * (Socket and Embedded). + * + * @author Pedro Ferreira */ public interface IMonetDBLanguage {
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/MCLException.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/MCLException.java @@ -11,6 +11,8 @@ package nl.cwi.monetdb.mcl.connection; /** * A general purpose Exception class for MCL related problems. This class should be used if no more precise Exception * class exists. + * + * @author Fabian Groffen */ public class MCLException extends Exception {
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/SenderThread.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/SenderThread.java @@ -22,6 +22,8 @@ import java.util.concurrent.locks.Reentr * it might be desirable that the driver as a whole does not block. This thread facilitates the prevention of such * 'full block', because this separate thread only will block.<br /> This thread is designed for reuse, as thread * creation costs are high. + * + * @author Fabian Groffen */ public class SenderThread extends Thread { @@ -76,8 +78,7 @@ public class SenderThread extends Thread this.error = e.getMessage(); } - // update our state, and notify, maybe someone is waiting - // for us in throwErrors + // update our state, and notify, maybe someone is waiting for us in throwErrors this.state = SenderThread.WAIT; this.waiting.signal(); }
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/BufferReallocator.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/BufferReallocator.java @@ -10,13 +10,29 @@ package nl.cwi.monetdb.mcl.connection.he import java.nio.CharBuffer; +/** + * An helper class to reallocate CharBuffer instance in way that won't overflow their capacity. (Adapted from the + * {@link StringBuilder} reallocation implementation). + * + * @author Pedro Ferreira + */ public final class BufferReallocator { + /** + * The possible MAX_ARRAY_SIZE, according to {@link AbstractStringBuilder}. + */ 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; + /** + * Calculates the CharBuffer's new capacity, throwing a {@link OutOfMemoryError}, if the capacity causes overflow. + * The capacity will always try to duplicate. + * + * @param buffer The buffer whose capacity will be expanded + * @return The buffer's new capacity + */ + private static int GetNewCapacity(CharBuffer buffer) { + int minCapacity = buffer.capacity() << 1; + int newCapacity = (buffer.capacity() << 1) + 2; if (newCapacity - minCapacity < 0) { newCapacity = minCapacity; } @@ -31,18 +47,32 @@ public final class BufferReallocator { } } - public static CharBuffer ReallocateBuffer(CharBuffer oldBuffer) { - int newCapacity = GetNewCapacity(oldBuffer); + /** + * Reallocates the buffer by creating a new one with the new capacity and the contents of the previous one. + * + * @param buffer The buffer whose capacity will be expanded + * @return The new buffer allocated + */ + public static CharBuffer ReallocateBuffer(CharBuffer buffer) { + int newCapacity = GetNewCapacity(buffer); CharBuffer newBuffer = CharBuffer.wrap(new char[newCapacity]); - oldBuffer.flip(); - newBuffer.put(oldBuffer.array()); + buffer.flip(); + newBuffer.put(buffer.array()); return newBuffer; } - public static CharBuffer EnsureCapacity(CharBuffer oldBuffer, int newCapacity) { - if(newCapacity > oldBuffer.capacity()) { - oldBuffer = CharBuffer.wrap(new char[newCapacity]); + /** + * Ensures that a buffer has a certain amount of capacity, creating a new one if the new capacity is larger than the + * current one in the buffer + * + * @param buffer The buffer whose capacity will be checked + * @param capacityThreshold The capacity threshold to test + * @return The original buffer or the new one allocated + */ + public static CharBuffer EnsureCapacity(CharBuffer buffer, int capacityThreshold) { + if(capacityThreshold > buffer.capacity()) { + buffer = CharBuffer.wrap(new char[capacityThreshold]); } - return oldBuffer; + return buffer; } }
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/ChannelSecurity.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/ChannelSecurity.java @@ -11,15 +11,20 @@ package nl.cwi.monetdb.mcl.connection.he import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +/** + * A helper class to process Hash digests during the authentication process. + * + * @author Fabian Groffen, Pedro Ferreira + */ public final class ChannelSecurity { private static char HexChar(int n) { return (n > 9) ? (char) ('a' + (n - 10)) : (char) ('0' + n); } /** - * Small helper method to convert a byte string to a hexadecimalstring representation. + * Helper method to convert a byte string to a hexadecimal String representation. * - * @param digest the byte array to convert - * @return the byte array as hexadecimal string + * @param digest The byte array to convert + * @return The byte array as a hexadecimal string */ private static String ToHex(byte[] digest) { char[] result = new char[digest.length * 2]; @@ -31,6 +36,13 @@ public final class ChannelSecurity { return new String(result); } + /** + * Digests several byte[] into a String digest, using a specified hash algorithm. + * + * @param algorithm The hash algorithm to use + * @param toDigests The Strings to digest + * @return The Strings digest as a hexadecimal string + */ public static String DigestStrings(String algorithm, byte[]... toDigests) { try { MessageDigest md = MessageDigest.getInstance(algorithm);
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/GregorianCalendarParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/GregorianCalendarParser.java @@ -17,12 +17,26 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; +/** + * A helper class to process MAPI dates, times and timestamps Strings into their Java representation. + * + * @author Fabian Groffen, Pedro Ferreira + */ public final class GregorianCalendarParser { + /** + * The date parser, with intention for re-usage to save memory allocations. + */ private static final SimpleDateFormat DateParser = new SimpleDateFormat("yyyy-MM-dd"); + /** + * The time parser, with intention for re-usage to save memory allocations. + */ private static final SimpleDateFormat TimeParser = new SimpleDateFormat("HH:mm:ss"); + /** + * The timestamp parser, with intention for re-usage to save memory allocations. + */ private static final SimpleDateFormat TimestampParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** @@ -43,6 +57,14 @@ public final class GregorianCalendarPars } } + /** + * Parses a date MAPI String into a Java {@link Calendar} instance. + * + * @param toParse The date String to parse + * @param pos The position of the String to start the parsing + * @return A {@link Calendar} instance of the parsed date + * @throws ProtocolException If the String could not be parsed + */ public static Calendar ParseDate(String toParse, ParsePosition pos) throws ProtocolException { pos.setIndex(0); Calendar res = new GregorianCalendar(); @@ -55,8 +77,18 @@ public final class GregorianCalendarPars return res; } - private static Calendar ParseTimeIn(String toParse, ParsePosition pos, boolean hasTimeZone, - SimpleDateFormat parser) throws ProtocolException { + /** + * Parses a time or a timestamp MAPI String into a Java {@link Calendar} instance. + * + * @param toParse The time/timestamp String to parse + * @param pos The position of the String to start the parsing + * @param hasTimeZone If the time/timestamp String has timezone information + * @param parser The parser to use (time or timestamp) + * @return A {@link Calendar} instance of the parsed time/timestamp + * @throws ProtocolException If the String could not be parsed + */ + private static Calendar ParseTimeIn(String toParse, ParsePosition pos, boolean hasTimeZone, SimpleDateFormat parser) + throws ProtocolException { pos.setIndex(0); Calendar res = new GregorianCalendar(); Date util = parser.parse(toParse, pos); @@ -95,10 +127,28 @@ public final class GregorianCalendarPars return res; } + /** + * Parses a time MAPI String into a Java {@link Calendar} instance. + * + * @param toParse The time String to parse + * @param pos The position of the String to start the parsing + * @param hasTimeZone If the time String has timezone information + * @return A {@link Calendar} instance of the parsed time + * @throws ProtocolException If the String could not be parsed + */ public static Calendar ParseTime(String toParse, ParsePosition pos, boolean hasTimeZone) throws ProtocolException { return ParseTimeIn(toParse, pos, hasTimeZone, TimeParser); } + /** + * Parses a timestamp MAPI String into a Java {@link Calendar} instance. + * + * @param toParse The timestamp String to parse + * @param pos The position of the String to start the parsing + * @param hasTimeZone If the timestamp String has timezone information + * @return A {@link Calendar} instance of the parsed timestamp + * @throws ProtocolException If the String could not be parsed + */ public static Calendar ParseTimestamp(String toParse, ParsePosition pos, boolean hasTimeZone) throws ProtocolException { return ParseTimeIn(toParse, pos, hasTimeZone, TimestampParser);
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java @@ -21,71 +21,138 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; +/** + * An abstract class to be extended by a JDBC socket connection. The base idea of this class is to allow easy + * integrations with future versions of the MAPI protocol. With new versions of the protocol, the way the data is + * fetched will be different hence this class should be sub-classed according to the protocol itself. + * <br/> + * Meanwhile the implementation of this class uses Java ByteBuffers which allows memory re-usage for more performance. + * Also MonetDB uses UTF-8 as its character encoding, hence its required to convert into UTF-16 (JVM encoding). + * + * @author Pedro Ferreira + */ public abstract class AbstractSocket implements Closeable { - + /** The TCP Socket to mserver */ protected final Socket socket; - + /** The MAPI connection this socket belong to */ protected final MapiConnection connection; - + /** ByteBuffer to read from the underlying socket InputStream */ private final ByteBuffer bufferIn; - + /** ByteBuffer to write into the underlying socket OutputStream */ private final ByteBuffer bufferOut; - - private final CharBuffer stringsEncoded; - + /** The bytes read from the bufferIn decoded into UTF-16 */ private final CharBuffer stringsDecoded; - - private final CharsetEncoder asciiEncoder = StandardCharsets.UTF_8.newEncoder(); - - private final CharsetDecoder asciiDecoder = StandardCharsets.UTF_8.newDecoder(); + /** The bytes to write into the bufferOut encoded into UTF-8 */ + private final CharBuffer stringsEncoded; + /** UTF-8 encoder */ + private final CharsetEncoder utf8Encoder = StandardCharsets.UTF_8.newEncoder(); + /** UTF-8 decoder */ + private final CharsetDecoder utf8Decoder = StandardCharsets.UTF_8.newDecoder(); 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()]); this.bufferOut = ByteBuffer.wrap(new byte[getBlockSize()]); - this.stringsEncoded = CharBuffer.allocate(getBlockSize()); this.stringsDecoded = CharBuffer.allocate(getBlockSize()); this.stringsDecoded.flip(); + this.stringsEncoded = CharBuffer.allocate(getBlockSize()); } + /** + * Get the socket timeout in milliseconds. + * + * @return The currently in use socket timeout in milliseconds + * @throws SocketException If an error in the underlying connection happened + */ int getSoTimeout() throws SocketException { return socket.getSoTimeout(); } + /** + * Sets the socket timeout in milliseconds. + * + * @param s The socket timeout in milliseconds + * @throws SocketException If an error in the underlying connection happened + */ void setSoTimeout(int s) throws SocketException { socket.setSoTimeout(s); } + /** + * Sets the TCP no delay feature in the underlying socket. + * + * @param on A true or false value + * @throws SocketException If an error in the underlying connection happened + */ void setTcpNoDelay(boolean on) throws SocketException { socket.setTcpNoDelay(on); } + /** + * Sets the underlying socket Endianness. + * + * @param bo A ByteOrder order value either Little-endian or Big-endian + */ void setSocketChannelEndianness(ByteOrder bo) { this.bufferIn.order(bo); this.bufferOut.order(bo); } + /** + * Gets the underlying socket block size. + * + * @return The underlying socket block size + */ public abstract int getBlockSize(); + /** + * Reads from the underlying socket into the bufferIn. + * + * @return The number off bytes read + * @throws IOException If an error in the underlying connection happened + */ abstract int readToBufferIn(ByteBuffer bufferIn) throws IOException; + /** + * Writes from bufferOut into the underlying socket. + * + * @return The number off bytes written + * @throws IOException If an error in the underlying connection happened + */ abstract int writeFromBufferOut(ByteBuffer bufferOut) throws IOException; + /** + * Flushes the output. + * + * @throws IOException If an error in the underlying connection happened + */ abstract void flush() throws IOException; + /** + * Helper method to read and decode UTF-8 data. + * + * @throws IOException If an error in the underlying connection happened + */ private void readToBuffer() throws IOException { int read = this.readToBufferIn(this.bufferIn); if(read == 0) { throw new IOException("The server has reached EOF!"); } this.stringsDecoded.clear(); - this.asciiDecoder.reset(); - this.asciiDecoder.decode(this.bufferIn, this.stringsDecoded,true); - this.asciiDecoder.flush(this.stringsDecoded); + this.utf8Decoder.reset(); + this.utf8Decoder.decode(this.bufferIn, this.stringsDecoded,true); + this.utf8Decoder.flush(this.stringsDecoded); this.stringsDecoded.flip(); } + /** + * Reads a line into the input lineBuffer, reallocating it if necessary. + * + * @param lineBuffer The buffer the data will be read into + * @return The input lineBuffer + * @throws IOException If an error in the underlying connection happened + */ public CharBuffer readLine(CharBuffer lineBuffer) throws IOException { lineBuffer.clear(); boolean found = false; @@ -119,11 +186,16 @@ public abstract class AbstractSocket imp return lineBuffer; } + /** + * Helper method to write, encode into UTF-8 and flush. + * + * @throws IOException If an error in the underlying connection happened + */ private void flushOutputCharBuffer() throws IOException { this.stringsEncoded.flip(); - this.asciiEncoder.reset(); - this.asciiEncoder.encode(this.stringsEncoded, this.bufferOut, true); - this.asciiEncoder.flush(this.bufferOut); + this.utf8Encoder.reset(); + this.utf8Encoder.encode(this.stringsEncoded, this.bufferOut, true); + this.utf8Encoder.flush(this.bufferOut); this.stringsEncoded.clear(); int written = this.writeFromBufferOut(this.bufferOut); if(written == 0) { @@ -133,6 +205,11 @@ public abstract class AbstractSocket imp } } + /** + * Writes a String line into the underlying socket. + * + * @throws IOException If an error in the underlying connection happened + */ private void writeNextBlock(String line) throws IOException { int limit = line.length(); int destinationPosition = this.stringsEncoded.position(); @@ -149,6 +226,14 @@ public abstract class AbstractSocket imp this.stringsEncoded.position(destinationPosition); } + /** + * Writes a String line as well a String prefix and suffix if supplied. + * + * @param prefix The prefix to write before the line if provided + * @param line The line to write into the socket + * @param suffix The suffix to write after the line if provided + * @throws IOException If an error in the underlying connection happened + */ public void writeNextLine(String prefix, String line, String suffix) throws IOException { if(prefix != null) { this.writeNextBlock(prefix);
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java @@ -13,7 +13,6 @@ import nl.cwi.monetdb.mcl.connection.hel import nl.cwi.monetdb.mcl.connection.ControlCommands; import nl.cwi.monetdb.mcl.connection.MCLException; import nl.cwi.monetdb.mcl.protocol.ProtocolException; -import nl.cwi.monetdb.mcl.protocol.AbstractProtocol; import nl.cwi.monetdb.mcl.protocol.ServerResponses; import nl.cwi.monetdb.mcl.protocol.oldmapi.OldMapiProtocol; @@ -23,9 +22,15 @@ import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteOrder; +import java.sql.Connection; import java.sql.SQLException; import java.util.*; +/** + * A {@link Connection} suitable for the MonetDB database on a MAPI connection. + * + * @author Fabian Groffen, Martin van Dinther, Pedro Ferreira + */ public class MapiConnection extends MonetConnection { /** the PROMPT ASCII char sent by the server */ static final char PROMPT_CHAR = '.'; @@ -36,6 +41,8 @@ public class MapiConnection extends Mone private final String hostname; /** The port to connect on the host to */ private final int port; + /** The database to connect to */ + private String database; /** The TCP Socket timeout in milliseconds. Default is 0 meaning the timeout is disabled (i.e., timeout of infinity) */ private int soTimeout = 0; /** Whether we should follow redirects */ @@ -44,93 +51,49 @@ public class MapiConnection extends Mone private int ttl = 10; /** Protocol version of the connection */ private int version; - /** The database to connect to */ - private String database; /** Endianness of the server */ private ByteOrder serverEndianness; - public MapiConnection(Properties props, String hash, String language, boolean blobIsBinary, - boolean clobIsLongChar, String hostname, int port, String database) throws IOException { + public MapiConnection(Properties props, String hash, String language, boolean blobIsBinary, boolean clobIsLongChar, + String hostname, int port, String database) throws IOException { super(props, hash, MapiLanguage.GetLanguageFromString(language), blobIsBinary, clobIsLongChar); this.hostname = hostname; this.port = port; this.database = database; } + /** + * Gets the hostname of the server used on this connection. + * + * @return The hostname of the server used on this connection + */ public String getHostname() { return hostname; } + /** + * Gets the port of the server used on this connection. + * + * @return The port of the server used on this connection + */ public int getPort() { return port; } + /** + * Gets the database to connect to. If database is null, a connection is made to the default database of the server. + * This is also the default. + * + * @return The database name + */ public String getDatabase() { return database; } - public boolean isFollowRedirects() { - return followRedirects; - } - - public int getTtl() { - return ttl; - } - - /** - * Sets whether MCL redirections should be followed or not. If set to false, an MCLException will be thrown when a - * redirect is encountered during connect. The default behaviour is to automatically follow redirects. - * - * @param r whether to follow redirects (true) or not (false) - */ - public void setFollowRedirects(boolean r) { - this.followRedirects = r; - } - - /** - * Sets the number of redirects that are followed when followRedirects is true. In order to avoid going into an - * endless loop due to some evil server, or another error, a maximum number of redirects that may be followed can be - * set here. Note that to disable the following of redirects you should use setFollowRedirects. - * - * @see #setFollowRedirects(boolean r) - * @param t the number of redirects before an exception is thrown - */ - public void setTTL(int t) { - this.ttl = t; - } - - /** - * Returns the mapi protocol version used by this socket. The protocol version depends on the server being used. - * Users of the MapiSocket should check this version to act appropriately. - * - * @return the mapi protocol version - */ - public int getProtocolVersion() { - return this.version; - } - - public int getVersion() { - return version; - } - - public ByteOrder getServerEndianness() { - return serverEndianness; - } - - @Override - public int getBlockSize() { - return ((OldMapiProtocol)protocol).getSocket().getBlockSize(); - } - - @Override - public int getDefFetchsize() { - return DEF_FETCHSIZE; - } - /** * Gets the SO_TIMEOUT from the underlying Socket. * - * @return the currently in use timeout in milliseconds + * @return The currently in use timeout in milliseconds */ @Override public int getSoTimeout() { @@ -150,23 +113,83 @@ public class MapiConnection extends Mone * setting can be useful to break out of this indefinite wait. This option must be enabled prior to entering the * blocking operation to have effect. * - * @param s The specified timeout, in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param timeout The specified timeout, in milliseconds. A timeout of zero is interpreted as an infinite timeout */ @Override - public void setSoTimeout(int s) { - if (s < 0) { + public void setSoTimeout(int timeout) { + if (timeout < 0) { throw new IllegalArgumentException("Timeout can't be negative"); } try { if(protocol != null) { - ((OldMapiProtocol)protocol).getSocket().setSoTimeout(s); + ((OldMapiProtocol)protocol).getSocket().setSoTimeout(timeout); } - this.soTimeout = s; + this.soTimeout = timeout; } catch (SocketException e) { this.addWarning("The socket timeout could not be set", "M1M05"); } } + /** + * Gets whether MCL redirections should be followed or not. If set to false, an MCLException will be thrown when a + * redirect is encountered during connect. The default behaviour is to automatically follow redirects. + * + * @return Whether to follow redirects (true) or not (false) + */ + public boolean isFollowRedirects() { + return followRedirects; + } + + /** + * Gets the number of redirects that are followed when followRedirects is true. In order to avoid going into an + * endless loop due to some evil server, or another error, a maximum number of redirects that may be followed can be + * set here. Note that to disable the following of redirects you should use setFollowRedirects. + * + * @see #isFollowRedirects() + * @return The number of redirects before an exception is thrown + */ + public int getTtl() { + return ttl; + } + + /** + * Gets the mapi protocol version used by this socket. The protocol version depends on the server being used. + * + * @return The mapi protocol version used by this socket + */ + public int getVersion() { + return version; + } + + /** + * Gets the connection server endianness. + * + * @return The connection server endianness + */ + public ByteOrder getServerEndianness() { + return serverEndianness; + } + + /** + * On a MAPI connection, the block size will be the block size of the connection. + * + * @return The block size length + */ + @Override + public int getBlockSize() { + return ((OldMapiProtocol)protocol).getSocket().getBlockSize(); + } + + /** + * On a MAPI connection the default fetch size per DataBlock is 250 rows. + * + * @return The default fetch size + */ + @Override + public int getDefFetchsize() { + return DEF_FETCHSIZE; + } + @Override public synchronized void closeUnderlyingConnection() throws IOException { ((OldMapiProtocol)protocol).getSocket().close(); @@ -177,27 +200,13 @@ public class MapiConnection extends Mone String res = "jdbc:monetdb://" + this.hostname + ":" + this.port + "/" + this.database; if (this.getLanguage() == MapiLanguage.LANG_MAL) res += "?language=mal"; - return res; + return res; } @Override - public AbstractProtocol getProtocol() { - return this.protocol; - } - - /** - * Sends the given string to MonetDB as control statement, making - * sure there is a prompt after the command is sent. All possible - * returned information is discarded. Encountered errors are - * reported. - * - * @param con the command to send - * @throws SQLException if an IO exception or a database error occurs - */ - @Override - public void sendControlCommand(int con, int data) throws SQLException { + public void sendControlCommand(int commandID, int data) throws SQLException { String command = null; - switch (con) { + switch (commandID) { case ControlCommands.AUTO_COMMIT: command = "auto_commit " + ((data == 1) ? "1" : "0"); break; @@ -211,7 +220,8 @@ public class MapiConnection extends Mone command = "close " + data; } try { - protocol.writeNextQuery(language.getCommandTemplateIndex(0), command, language.getCommandTemplateIndex(1)); + protocol.writeNextQuery(language.getCommandTemplateIndex(0), command, + language.getCommandTemplateIndex(1)); protocol.waitUntilPrompt(); int csrh = protocol.getCurrentServerResponse(); if (csrh == ServerResponses.ERROR) { @@ -227,10 +237,20 @@ public class MapiConnection extends Mone } @Override - public ResponseList createResponseList(int fetchSize, int maxRows, int resultSetType, int resultSetConcurrency) throws SQLException { + public ResponseList createResponseList(int fetchSize, int maxRows, int resultSetType, int resultSetConcurrency) + throws SQLException { return new MonetConnection.ResponseList(fetchSize, maxRows, resultSetType, resultSetConcurrency); } + /** + * Connects to the given host and port, logging in as the given user. If followRedirect is false, a + * RedirectionException is thrown when a redirect is encountered. + * + * @return A List with informational (warning) messages. If this list is empty; then there are no warnings. + * @throws IOException if an I/O error occurs when creating the socket + * @throws ProtocolException if bogus data is received + * @throws MCLException if an MCL related error occurs + */ @Override public List<String> connect(String user, String pass) throws IOException, ProtocolException, MCLException { // Wrap around the internal connect that needs to know if it should really make a TCP connection or not. @@ -383,8 +403,8 @@ public class MapiConnection extends Mone * @param hash the hash method(s) to use, or NULL for all supported hashes */ private String getChallengeResponse(String chalstr, String username, String password, String language, - String database, String hash) - throws ProtocolException, MCLException, IOException { + String database, String hash) throws ProtocolException, MCLException, + IOException { String response; String algo;
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiLanguage.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiLanguage.java @@ -10,6 +10,11 @@ package nl.cwi.monetdb.mcl.connection.ma import nl.cwi.monetdb.mcl.connection.IMonetDBLanguage; +/** + * The MAPI implementation of the available languages on a JDBC connection: SQL and MAL. + * + * @author Pedro Ferreira + */ public enum MapiLanguage implements IMonetDBLanguage { /** the SQL language */
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/OldMapiSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/OldMapiSocket.java @@ -13,18 +13,45 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +/** + * A Socket for communicating with the MonetDB database in MAPI block mode. The OldMapiSocket implements the protocol + * specifics of the MAPI block mode protocol, + * + * For each line read, it is determined what type of line it is according to the MonetDB MAPI protocol version 9. This + * results in a line to be PROMPT, HEADER, RESULT, ERROR or UNKNOWN. + * + * The general use of this Socket must be seen only in the full context of a MAPI connection to a server. It has the + * same ingredients as a normal Socket, allowing for seamless plugging. + * <pre> + * Socket \ / InputStream ----> + * > o < + * MapiSocket / \ OutputStream ----> + * </pre> + * The OldMapiSocket allows to retrieve Streams for communicating. They are interfaced, so they can be chained in any + * way. While the Socket transparently deals with how data is sent over the wire, the actual data read needs to be + * interpreted, for which a Reader/Writer interface is most sufficient. + * + * @author Fabian Groffen + * @version 4.1 + */ public class OldMapiSocket extends AbstractSocket { /** The blocksize (hardcoded in compliance with stream.mx) */ public final static int BLOCK = 8 * 1024 - 2; /** - * A short in two bytes for holding the block size in bytes + * A short in two bytes for holding the block size in bytes. */ private final byte[] blklen = new byte[2]; + /** + * The socket input stream read by blocks. + */ private final OldMapiBlockInputStream inStream; + /** + * The socket output stream written by blocks. + */ private final OldMapiBlockOutputStream outStream; OldMapiSocket(String hostname, int port, MapiConnection connection) throws IOException { @@ -33,6 +60,9 @@ public class OldMapiSocket extends Abstr this.outStream = new OldMapiBlockOutputStream(socket.getOutputStream()); } + /** + * The block size will be the one hardcoded on the connection. + */ @Override public int getBlockSize() { return BLOCK; @@ -58,6 +88,9 @@ public class OldMapiSocket extends Abstr this.socket.close(); } + /** + * Inner class that is used to make the data on the blocked stream available as a normal stream. + */ private class OldMapiBlockInputStream { private final InputStream inStream; @@ -80,14 +113,11 @@ public class OldMapiSocket extends Abstr } /** - * Small wrapper to get a blocking variant of the read() method - * on the BufferedInputStream. We want to benefit from the - * Buffered pre-fetching, but not dealing with half blocks. - * Changing this class to be able to use the partially received - * data will greatly complicate matters, while a performance - * improvement is debatable given the relatively small size of - * our blocks. Maybe it does speed up on slower links, then - * consider this method a quick bug fix/workaround. + * Small wrapper to get a blocking variant of the read() method on the BufferedInputStream. We want to benefit + * from the Buffered pre-fetching, but not dealing with half blocks. Changing this class to be able to use the + * partially received data will greatly complicate matters, while a performance improvement is debatable given + * the relatively small size of our blocks. Maybe it does speed up on slower links, then consider this method a + * quick bug fix/workaround. * * @return false if reading the block failed due to EOF */ @@ -112,27 +142,19 @@ public class OldMapiSocket extends Abstr } /** - * Reads the next block on the stream into the internal buffer, - * or writes the prompt in the buffer. + * Reads the next block on the stream into the internal buffer, or writes the prompt in the buffer. + * <p> + * The blocked stream protocol consists of first a two byte integer indicating the length of the block, then the + * block, followed by another length + block. The end of such sequence is put in the last bit of the length, and + * hence this length should be shifted to the right to obtain the real length value first. We simply fetch + * blocks here as soon as they are needed for the stream's read methods. * <p> - * The blocked stream protocol consists of first a two byte - * integer indicating the length of the block, then the - * block, followed by another length + block. The end of - * such sequence is put in the last bit of the length, and - * hence this length should be shifted to the right to - * obtain the real length value first. We simply fetch - * blocks here as soon as they are needed for the stream's - * read methods. + * The user-flush, which is an implicit effect of the end of a block sequence, is communicated beyond the stream + * by inserting a prompt sequence on the stream after the last block. This method makes sure that a final block + * ends with a newline, if it doesn't already, in order to facilitate a Reader that is possibly chained to this + * InputStream. * <p> - * The user-flush, which is an implicit effect of the end of - * a block sequence, is communicated beyond the stream by - * inserting a prompt sequence on the stream after the last - * block. This method makes sure that a final block ends with a - * newline, if it doesn't already, in order to facilitate a - * Reader that is possibly chained to this InputStream. - * <p> - * If the stream is not positioned correctly, hell will break - * loose. + * If the stream is not positioned correctly, hell will break loose. */ private int readBlock() throws IOException { // read next two bytes (short) @@ -227,6 +249,12 @@ public class OldMapiSocket extends Abstr } } + /** + * Inner class that is used to write data on a normal stream as a blocked stream. A call to the flush() method will + * write a "final" block to the underlying stream. Non-final blocks are written as soon as one or more bytes would + * not fit in the current block any more. This allows to write to a block to it's full size, and then flush it + * explicitly to have a final block being written to the stream. + */ class OldMapiBlockOutputStream { private final OutputStream outStream;
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java @@ -108,7 +108,7 @@ public abstract class AbstractProtocol { * @param columnNames The column names array * @param columnLengths The column lengths array * @param types The columns SQL names array - * @param tableNames The columns schemas and names in format schema.name + * @param tableNames The columns schemas and names in format schema.table * @return A TableResultHeaders integer representation, representing which of the fields was filled * @throws ProtocolException If an error in the underlying connection happened. */
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/ProtocolException.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/ProtocolException.java @@ -15,6 +15,8 @@ import java.text.ParseException; * throws an ProtocolException as soon as something that is read cannot be understood or does not conform to the * specifications (e.g. a missing field). The instance that throws the exception will try to give an error offset * whenever possible. Alternatively it makes sure that the error message includes the offending data read. + * + * @author Fabian Groffen */ public class ProtocolException extends ParseException {
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/ServerResponses.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/ServerResponses.java @@ -10,6 +10,8 @@ package nl.cwi.monetdb.mcl.protocol; /** * This class represents the possible stages of a query response by the server. + * + * @author Fabian Groffen, Pedro Ferreira */ public final class ServerResponses {
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/StarterHeaders.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/StarterHeaders.java @@ -11,6 +11,8 @@ package nl.cwi.monetdb.mcl.protocol; /** * This class lists the possible responses of a query by the server. Notice that Q_PARSE is not used by neither a MAPI * or an embedded connection, so it's here for completeness. + * + * @author Fabian Groffen, Pedro Ferreira */ public final class StarterHeaders {
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/TableResultHeaders.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/TableResultHeaders.java @@ -10,7 +10,9 @@ package nl.cwi.monetdb.mcl.protocol; /** * This class lists the result table headers returned by the server. The integer values are used for the bitmap on the - * ResultSetResponse Class + * ResultSetResponse Class. + * + * @author Fabian Groffen, Pedro Ferreira */ public final class TableResultHeaders {
deleted file mode 100644 --- a/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright 1997 - July 2008 CWI, August 2008 - 2017 MonetDB B.V. - */ - -package nl.cwi.monetdb.mcl.protocol.newmapi; - -import nl.cwi.monetdb.jdbc.MonetConnection; -import nl.cwi.monetdb.mcl.protocol.*; -import nl.cwi.monetdb.mcl.responses.AutoCommitResponse; -import nl.cwi.monetdb.mcl.responses.DataBlockResponse; -import nl.cwi.monetdb.mcl.responses.ResultSetResponse; -import nl.cwi.monetdb.mcl.responses.UpdateResponse; - -import java.io.IOException; -import java.util.Map; - -public class NewMapiProtocol extends AbstractProtocol { - - @Override - public int getCurrentServerResponse() { - return 0; - } - - @Override - public void waitUntilPrompt() throws IOException { - - } - - @Override - public void fetchNextResponseData() throws IOException { - - } - - @Override - public int getNextStarterHeader() { - return 0; - } - - @Override - public ResultSetResponse getNextResultSetResponse(MonetConnection con, MonetConnection.ResponseList list, int seqnr) throws ProtocolException { - return null; - } - - @Override - public UpdateResponse getNextUpdateResponse() throws ProtocolException { - return null; - } - - @Override - public AutoCommitResponse getNextAutoCommitResponse() throws ProtocolException { - return null; - } - - @Override - public DataBlockResponse getNextDatablockResponse(Map<Integer, ResultSetResponse> rsresponses) throws ProtocolException { - return null; - } - - @Override - public int getNextTableHeader(String[] columnNames, int[] columnLengths, String[] types, String[] tableNames) throws ProtocolException { - return 0; - } - - @Override - public int parseTupleLines(int lineNumber, int[] typesMap, Object[] values) throws ProtocolException { - return 0; - } - - @Override - public String getRemainingStringLine(int startIndex) { - return null; - } - - @Override - public synchronized void writeNextQuery(String prefix, String query, String suffix) throws IOException { - - } -}
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java @@ -22,14 +22,33 @@ import java.io.IOException; import java.nio.CharBuffer; import java.util.Map; +/** + * The JDBC abstract protocol implementation on a MAPI connection using the protocol version 9. The connection holds a + * lineBuffer which will be reused during the whole connection for memory saving purposes. An additional tupleLineBuffer + * is used to help parsing tuple lines from a BLOCK response. + * + * @author Pedro Ferreira + */ public class OldMapiProtocol extends AbstractProtocol { + /** + * The current server response. + */ private int currentServerResponseHeader = ServerResponses.UNKNOWN; + /** + * The underlying MAPI socket connection. + */ private final OldMapiSocket socket; + /** + * The buffer used to parse server's responses. + */ CharBuffer lineBuffer; + /** + * A helper buffer used to parse tuple line responses. + */ CharBuffer tupleLineBuffer; public OldMapiProtocol(OldMapiSocket socket) { @@ -38,6 +57,11 @@ public class OldMapiProtocol extends Abs this.tupleLineBuffer = CharBuffer.wrap(new char[1024]); } + /** + * Retrieve the underlying socket data. + * + * @return The underlying socket data + */ public OldMapiSocket getSocket() { return socket; } @@ -47,6 +71,14 @@ public class OldMapiProtocol extends Abs return currentServerResponseHeader; } + /** + * Reads up till the MonetDB prompt, indicating the server is ready for a new command. + * + * 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. + * + * @throws IOException if an IO exception occurs while talking to the server + */ @Override public void waitUntilPrompt() throws IOException { while(this.currentServerResponseHeader != ServerResponses.PROMPT) { @@ -62,14 +94,23 @@ public class OldMapiProtocol extends Abs } } + /** + * Read a line of text from the socket. A line is considered to be terminated by any one of a line feed ('\n'). + * + * 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). + * + * @throws IOException If an I/O error occurs + */ @Override - public void fetchNextResponseData() throws IOException { //readLine equivalent + public void fetchNextResponseData() throws IOException { 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.lineBuffer.toString().matches("^[0-9A-Z]{5}!.+")) { + 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()); @@ -97,7 +138,7 @@ public class OldMapiProtocol extends Abs @Override public UpdateResponse getNextUpdateResponse() throws ProtocolException { int count = OldMapiStartOfHeaderParser.GetNextResponseDataAsInt(this); //The order cannot be switched!! - int lastId = OldMapiStartOfHeaderParser.GetNextResponseDataAsInt(this); //TODO test this!! + int lastId = OldMapiStartOfHeaderParser.GetNextResponseDataAsInt(this); return new UpdateResponse(lastId, count); } @@ -125,7 +166,8 @@ public class OldMapiProtocol extends Abs @Override public int getNextTableHeader(String[] columnNames, int[] columnLengths, String[] types, String[] tableNames) throws ProtocolException { - return OldMapiTableHeaderParser.GetNextTableHeader(this.lineBuffer, columnNames, columnLengths, types, tableNames); + return OldMapiTableHeaderParser.GetNextTableHeader(this.lineBuffer, columnNames, columnLengths, types, + tableNames); } @Override @@ -146,6 +188,7 @@ public class OldMapiProtocol extends Abs @Override public synchronized void writeNextQuery(String prefix, String query, String suffix) throws IOException { this.socket.writeNextLine(prefix, query, suffix); - this.currentServerResponseHeader = ServerResponses.UNKNOWN; //reset reader state + // reset reader state, last line isn't valid any more now + this.currentServerResponseHeader = ServerResponses.UNKNOWN; } }
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiServerResponseParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiServerResponseParser.java @@ -10,8 +10,19 @@ package nl.cwi.monetdb.mcl.protocol.oldm import nl.cwi.monetdb.mcl.protocol.ServerResponses; +/** + * This class parses the next server response on a MAPI connection using the next ASCII character on the stream. + * + * @author Fabian Groffen, Pedro Ferreira + */ final class OldMapiServerResponseParser { + /** + * Retrieves the next server response from an old MAPI protocol instance. + * + * @param protocol An Old MAPI protocol instance from which the next server response will be retrieved + * @return The integer representation of the next server response + */ static int ParseOldMapiServerResponse(OldMapiProtocol protocol) { int res; switch (protocol.lineBuffer.get()) {
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiStartOfHeaderParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiStartOfHeaderParser.java @@ -11,6 +11,12 @@ package nl.cwi.monetdb.mcl.protocol.oldm import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.protocol.StarterHeaders; +/** + * The OldMapiStartOfHeaderParser is responsible to retrieve the server's headers on a SOHEADER response. Depending on + * the type of the header, the next tokens should be retrieved as integers or Strings. + * + * @author Fabian Groffen, Pedro Ferreira + */ final class OldMapiStartOfHeaderParser { static int GetNextStartHeaderOnOldMapi(OldMapiProtocol protocol) { @@ -47,6 +53,14 @@ final class OldMapiStartOfHeaderParser { return res; } + /** + * Returns the next token in the Protocol's lineBuffer as an integer. The value is considered to end at the end of + * the lineBuffer or at a space. If a non-numeric character is encountered a ProtocolException is thrown. + * + * @param protocol An Old Mapi Protocol instance where the next token will be retrieved + * @return The next token in the Protocol as an integer + * @throws ProtocolException if no numeric value could be read + */ static int GetNextResponseDataAsInt(OldMapiProtocol protocol) throws ProtocolException { int currentPointer = protocol.lineBuffer.position(); int limit = protocol.lineBuffer.limit(); @@ -83,6 +97,14 @@ final class OldMapiStartOfHeaderParser { return positive ? tmp : -tmp; } + /** + * Returns the next token in the Protocol's lineBuffer as a String. The value is considered to end at the end of the + * lineBuffer or at a space. If no character is found, a ProtocolException is thrown. + * + * @param protocol An Old Mapi Protocol instance where the next token will be retrieved + * @return The next token in the Protocol as a String + * @throws ProtocolException if no character could be read + */ static String GetNextResponseDataAsString(OldMapiProtocol protocol) throws ProtocolException { int currentPointer = protocol.lineBuffer.position(); int limit = protocol.lineBuffer.limit();
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java @@ -13,10 +13,27 @@ import nl.cwi.monetdb.mcl.protocol.Table import java.nio.CharBuffer; +/** + * The OldMapiTableHeaderParser is a generic parser that retrieves Q_TABLE, Q_PREPARE and Q_BLOCK responses data as + * integers and Strings to fill the Tables' metadata. + * + * @author Fabian Groffen, Pedro Ferreira + */ final class OldMapiTableHeaderParser { + /** + * Retrieves the next table result set header and fills the respective array of values. + * + * @param lineBuffer An Old Mapi Protocol's lineBuffer to retrieve data + * @param columnNames The result set column names + * @param columnLengths The result set column lengths + * @param types The result set column SQL types + * @param tableNames The result set columns schema and table names in format schema.table + * @return The integer representation of the Table Result Header retrieved + * @throws ProtocolException If an error while parsing occurred + */ static int GetNextTableHeader(CharBuffer lineBuffer, String[] columnNames, int[] columnLengths, - String[] types, String[] tableNames) throws ProtocolException { + String[] types, String[] tableNames) throws ProtocolException { int res = TableResultHeaders.UNKNOWN; int currentLength = lineBuffer.limit(); char[] array = lineBuffer.array(); @@ -50,7 +67,7 @@ final class OldMapiTableHeaderParser { } } if (!nameFound) - throw new ProtocolException("invalid header, no header name found", pos); + throw new ProtocolException("Invalid header, no header name found", pos); // depending on the name of the header, we continue switch (array[pos]) { @@ -76,11 +93,18 @@ final class OldMapiTableHeaderParser { } break; default: - throw new ProtocolException("unknown header: " + new String(array, pos, currentLength - pos)); + throw new ProtocolException("Unknown header: " + new String(array, pos, currentLength - pos)); } return res; } + /** + * Fills a String array header with values. + * + * @param array The lineBuffer's backing array + * @param stop The position to stop parsing + * @param stringValues The String array to fill + */ private static void GetStringValues(char[] array, int stop, String[] stringValues) { int elem = 0, start = 2; @@ -94,6 +118,14 @@ final class OldMapiTableHeaderParser { stringValues[elem] = new String(array, start, stop - start); } + /** + * Fills an integer array header with values. + * + * @param array The lineBuffer's backing array + * @param stop The position to stop parsing + * @param intValues The integer array to fill + * @throws ProtocolException If an error while parsing occurred + */ private static void GetIntValues(char[] array, int stop, int[] intValues) throws ProtocolException { int elem = 0, tmp = 0, start = 2; @@ -108,7 +140,7 @@ final class OldMapiTableHeaderParser { if (array[i] >= '0' && array[i] <= '9') { tmp += (int) array[i] - (int)'0'; } else { - throw new ProtocolException("expected a digit in " + new String(array) + " 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 @@ -20,12 +20,38 @@ import java.nio.CharBuffer; import java.sql.Types; import java.text.ParsePosition; import java.util.Calendar; -import java.util.Map; +/** + * The OldMapiTupleLineParser extracts the values from a given tuple. The number of values that are expected are known + * upfront to speed up allocation and validation. For null values we will map into Java minimum values for primitives + * and null pointers for objects. + * + * @author Fabian Groffen, Pedro Ferreira + */ final class OldMapiTupleLineParser { + /** + * The character array representation of a NULL value found in a tuple. + */ private static final char[] NULL_STRING = new char[]{'N','U','L','L'}; + /** + * A Java parser to parse Date, Time and Timestamp data, to be reused during the connection to save memory + * allocation. + */ + private static final ParsePosition Ppos = new ParsePosition(0); + + /** + * Parses the given OldMapiProtocol's lineBuffer source as tuple line for a DataBlock. If source cannot be parsed, a + * ProtocolException is thrown. The OldMapiProtocol's tupleLineBuffer is used to help parsing a column value. + * + * @param protocol The OldMapiProtocol instance to parse its lineBuffer + * @param lineNumber The row line number on the DataBlock to insert the tuple + * @param typesMap The JDBC types mapping array + * @param values An array of columns to fill the parsed values + * @return The number of columns parsed + * @throws ProtocolException If an error occurs during parsing + */ static int OldMapiParseTupleLine(OldMapiProtocol protocol, int lineNumber, int[] typesMap, Object[] values) throws ProtocolException { CharBuffer lineBuffer = protocol.lineBuffer; @@ -70,7 +96,6 @@ final class OldMapiTupleLineParser { } else if (!escaped) { inString = false; } - // reset escaped flag escaped = false; break; @@ -78,7 +103,7 @@ final class OldMapiTupleLineParser { if (!inString && (i > 0 && array[i - 1] == ',') || (i + 1 == len - 1 && array[++i] == ']')) { // dirty // split! if (array[cursor] == '"' && array[i - 2] == '"') { - // reuse the CharBuffer by cleaning it and ensure the capacity + // reuse the tupleLineBuffer by cleaning it and ensure the capacity tupleLineBuffer.clear(); tupleLineBuffer = BufferReallocator.EnsureCapacity(tupleLineBuffer, (i - 2) - (cursor + 1)); @@ -149,6 +174,14 @@ final class OldMapiTupleLineParser { return column; } + /** + * Parses a BLOB String from a tuple column, converting it into a Java byte[] representation. + * + * @param toParse The CharBuffer's backing array + * @param startPosition The first position in the array to parse + * @param count The number of characters to read from the starter position + * @return A Java byte[] instance with the parsed BLOB + */ private static byte[] BinaryBlobConverter(char[] toParse, int startPosition, int count) { int len = (startPosition + count) / 2; byte[] res = new byte[len]; @@ -158,8 +191,17 @@ final class OldMapiTupleLineParser { return res; } - private static final ParsePosition Ppos = new ParsePosition(0); - + /** + * Converts a segment of a CharBuffer's backing array into a Java primitive or object depending on the JDBC Mapping. + * + * @param toParse The CharBuffer's backing array + * @param startPosition The first position in the array to parse + * @param count The number of characters to read from the starter position + * @param lineNumber The row line number on the DataBlock to insert the tuple + * @param columnArray The column array where the value will be appended + * @param jDBCMapping The JDBC mapping of the value + * @throws ProtocolException If the JDBC Mapping is unknown + */ private static void OldMapiStringToJavaDataConversion(char[] toParse, int startPosition, int count, int lineNumber, Object columnArray, int jDBCMapping) throws ProtocolException { switch (jDBCMapping) { @@ -221,11 +263,19 @@ final class OldMapiTupleLineParser { ((byte[][]) columnArray)[lineNumber] = BinaryBlobConverter(toParse, startPosition, count); break; default: - throw new ProtocolException("Unknown type!"); + throw new ProtocolException("Unknown JDBC mapping!"); } } - private static void SetNullValue(int lineNumber, Object columnArray, int jDBCMapping) throws ProtocolException { + /** + * Maps MonetDB's null values with their respective Java representation. For the primitive types, we will map them + * to their minimum values, while for objects we just map into null pointers. + * + * @param lineNumber The row line number on the DataBlock to insert the tuple + * @param columnArray The column array where the value will be appended + * @param jDBCMapping The JDBC mapping of the value + */ + private static void SetNullValue(int lineNumber, Object columnArray, int jDBCMapping) { switch (jDBCMapping) { case Types.BOOLEAN: case Types.TINYINT:
--- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParserHelper.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParserHelper.java @@ -1,7 +1,35 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 1997 - July 2008 CWI, August 2008 - 2017 MonetDB B.V. + */ + package nl.cwi.monetdb.mcl.protocol.oldmapi; +import nl.cwi.monetdb.mcl.protocol.ProtocolException; + +/** + * This is a helper Class for the OldMapiTupleLineParser. The main objective of this class is to parse primitive types + * without any memory allocation for performance reasons. The code may seem to be boilerplate, but it has to be done + * this way to due to poor typing of the Java programming language. + * + * @author Pedro Ferreira + */ final class OldMapiTupleLineParserHelper { + /** + * Checks if a char[] (target) is inside on another (source), retrieving the first index on the source, where the + * target is, if found. In other words this a Java implementation of the strstr function from the C standard. + * As we search always from the beginning of the source, the start parameter is not used. + * + * @param source The source char[] to search + * @param sourceCount The number of characters in the source array to search + * @param target The target char[] to be found + * @param targetCount The result set column SQL types + * @return The integer representation of the Table Result Header retrieved + */ static int CharIndexOf(char[] source, int sourceCount, char[] target, int targetCount) { if (targetCount == 0) { return 0; @@ -31,24 +59,43 @@ final class OldMapiTupleLineParserHelper return -1; } + /** + * The character array representation of a TRUE value. + */ private static final char[] TrueConstant = new char[]{'t','r','u','e'}; + /** + * Converts a segment of a CharBuffer's backing array into a Java boolean. + * + * @param data The CharBuffer's backing array to parse + * @param start The first position in the array to parse + * @param count The number of characters to read from the starter position + * @return 1 it's a true value, 0 if false + */ static byte CharArrayToBoolean(char[] data, int start, int count) { return CharIndexOf(data, start + count, TrueConstant, 4) == start ? (byte)1 : (byte)0; } - static byte CharArrayToByte(char[] data, int start, int count) { + /** + * Converts a segment of a CharBuffer's backing array into a Java byte. + * + * @param data The CharBuffer's backing array to parse + * @param start The first position in the array to parse + * @param count The number of characters to read from the starter position + * @return The parsed byte value + */ + static byte CharArrayToByte(char[] data, int start, int count) throws ProtocolException { byte tmp = 0; int limit = start + count; boolean positive = true; char chr = data[start++]; - // note: don't use Character.isDigit() here, because we only want ISO-LATIN-1 digits + if (chr >= '0' && chr <= '9') { tmp = (byte)(chr - '0'); } else if(chr == '-') { positive = false; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } while (start < limit) { chr = data[start++]; @@ -59,24 +106,32 @@ final class OldMapiTupleLineParserHelper if (chr >= '0' && chr <= '9') { tmp += chr - '0'; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } } return positive ? tmp : (byte) -tmp; } - static short CharArrayToShort(char[] data, int start, int count) { + /** + * Converts a segment of a CharBuffer's backing array into a Java short. + * + * @param data The CharBuffer's backing array to parse + * @param start The first position in the array to parse + * @param count The number of characters to read from the starter position + * @return The parsed short value + */ + static short CharArrayToShort(char[] data, int start, int count) throws ProtocolException { short tmp = 0; int limit = start + count; boolean positive = true; char chr = data[start++]; - // note: don't use Character.isDigit() here, because we only want ISO-LATIN-1 digits + if (chr >= '0' && chr <= '9') { tmp = (short)(chr - '0'); } else if(chr == '-') { positive = false; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } while (start < limit) { chr = data[start++]; @@ -87,23 +142,31 @@ final class OldMapiTupleLineParserHelper if (chr >= '0' && chr <= '9') { tmp += chr - '0'; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } } return positive ? tmp : (short) -tmp; } - static int CharArrayToInt(char[] data, int start, int count) { + /** + * Converts a segment of a CharBuffer's backing array into a Java int. + * + * @param data The CharBuffer's backing array to parse + * @param start The first position in the array to parse + * @param count The number of characters to read from the starter position + * @return The parsed int value + */ + static int CharArrayToInt(char[] data, int start, int count) throws ProtocolException { int tmp = 0, limit = start + count; boolean positive = true; char chr = data[start++]; - // note: don't use Character.isDigit() here, because we only want ISO-LATIN-1 digits + if (chr >= '0' && chr <= '9') { tmp = chr - '0'; } else if(chr == '-') { positive = false; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } while (start < limit) { chr = data[start++]; @@ -116,24 +179,32 @@ final class OldMapiTupleLineParserHelper if (chr >= '0' && chr <= '9') { tmp += (int)chr - (int)'0'; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } } return positive ? tmp : -tmp; } - static long CharArrayToLong(char[] data, int start, int count) { + /** + * Converts a segment of a CharBuffer's backing array into a Java long. + * + * @param data The CharBuffer's backing array to parse + * @param start The first position in the array to parse + * @param count The number of characters to read from the starter position + * @return The parsed long value + */ + static long CharArrayToLong(char[] data, int start, int count) throws ProtocolException { long tmp = 0; int limit = start + count; boolean positive = true; char chr = data[start++]; - // note: don't use Character.isDigit() here, because we only want ISO-LATIN-1 digits + if (chr >= '0' && chr <= '9') { tmp = chr - '0'; } else if(chr == '-') { positive = false; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } while (start < limit) { chr = data[start++]; @@ -146,7 +217,7 @@ final class OldMapiTupleLineParserHelper if (chr >= '0' && chr <= '9') { tmp += chr - '0'; } else { - throw new NumberFormatException(); + throw new ProtocolException("Expected a digit at the position " + (start - 1)); } } return positive ? tmp : -tmp;
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/AutoCommitResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/AutoCommitResponse.java @@ -12,6 +12,8 @@ package nl.cwi.monetdb.mcl.responses; * The AutoCommitResponse represents a transaction message. It stores (a change in) the server side auto commit mode. * <br /> * <tt>&4 (t|f)</tt> + * + * @author Fabian Groffen */ public class AutoCommitResponse extends SchemaResponse {
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java @@ -33,6 +33,8 @@ import java.util.Calendar; * This object is not intended to be queried by multiple threads synchronously. It is designed to work for one thread * retrieving rows from it. When multiple threads will retrieve rows from this object, it is possible for threads to * get the same data. + * + * @author Fabian Groffen, Pedro Ferreira */ public class DataBlockResponse implements IIncompleteResponse {
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/IIncompleteResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/IIncompleteResponse.java @@ -14,6 +14,8 @@ import nl.cwi.monetdb.mcl.protocol.Proto /** * The ResultSetResponse and DatablockResponse Classes might require more than one Block response if the response is * larger than the BlockSize. + * + * @author Fabian Groffen, Pedro Ferreira */ public interface IIncompleteResponse extends IResponse {
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/IResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/IResponse.java @@ -11,6 +11,8 @@ package nl.cwi.monetdb.mcl.responses; /** * A Response is a message sent by the server to indicate some action has taken place, and possible results of that * action. + * + * @author Fabian Groffen, Pedro Ferreira */ public interface IResponse {
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java @@ -30,6 +30,8 @@ import java.sql.Types; * there the first line consists out of<br /> * <tt>&"qt" "id" "tc" "cc" "rc"</tt>. * Meanwhile on an Embedded connection the data is fetched with no parsing. + * + * @author Fabian Groffen, Pedro Ferreira */ public class ResultSetResponse implements IIncompleteResponse {
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/SchemaResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/SchemaResponse.java @@ -16,6 +16,8 @@ import java.sql.Statement; * MonetDB's case always SUCCESS_NO_INFO. Note that this state is not sent by the server. * <br /> * <tt>&3</tt> + * + * @author Fabian Groffen */ public class SchemaResponse implements IResponse {
--- a/src/main/java/nl/cwi/monetdb/mcl/responses/UpdateResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/UpdateResponse.java @@ -14,6 +14,8 @@ package nl.cwi.monetdb.mcl.responses; * inserted auto-generated ID, or -1 if not applicable. * <br /> * <tt>&2 0 -1</tt> + * + * @author Fabian Groffen, Pedro Ferreira */ public class UpdateResponse implements IResponse {