# HG changeset patch # User Pedro Ferreira <pedro.ferreira@monetdbsolutions.com> # Date 1481650530 -3600 # Node ID 953422c41194dac22e8f8fa7dd164f105968e412 # Parent af83fa389393516ae4569f4361b5393f1b2ef626 The data retrieval in ResultSets is now Column wise. Ready to start the embedded integrate, but it has to perform extra tests for the more rare types. diff --git a/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java b/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java --- a/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java @@ -3,7 +3,7 @@ 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.socket.MapiLanguage; +import nl.cwi.monetdb.mcl.connection.mapi.MapiLanguage; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.protocol.AbstractProtocol; import nl.cwi.monetdb.mcl.protocol.ServerResponses; @@ -1344,9 +1344,8 @@ public abstract class MonetConnection ex } /** - * A list of Response objects. Responses are added to this list. - * Methods of this class are not synchronized. This is left as - * responsibility to the caller to prevent concurrent access. + * A list of Response objects. Responses are added to this list. Methods of this class are not synchronized. This is + * left as responsibility to the caller to prevent concurrent access. */ public class ResponseList { @@ -1368,9 +1367,8 @@ public abstract class MonetConnection ex private int curResponse = -1; /** - * Main constructor. The query argument can either be a String - * or List. An SQLException is thrown if another object - * instance is supplied. + * Main constructor. The query argument can either be a String or List. An SQLException is thrown if another + * object instance is supplied. * * @param cachesize overall cachesize to use * @param maxrows maximum number of rows to allow in the set @@ -1419,8 +1417,7 @@ public abstract class MonetConnection ex } curResponse++; if (curResponse >= responses.size()) { - // ResponseList is obviously completed so, there are no - // more responses + // ResponseList is obviously completed so, there are no more responses return null; } else { // return this response diff --git a/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java b/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java --- a/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java @@ -293,12 +293,12 @@ final public class MonetDriver implement typeMap.put("date", Types.DATE); typeMap.put("decimal", Types.DECIMAL); typeMap.put("double", Types.DOUBLE); - typeMap.put("geometry", Types.VARCHAR); - typeMap.put("geometrya", Types.VARCHAR); + typeMap.put("geometry", Types.OTHER); + typeMap.put("geometrya", Types.OTHER); typeMap.put("hugeint", Types.NUMERIC); //but we will convert to java.math.BigInteger - typeMap.put("inet", Types.VARCHAR); + typeMap.put("inet", Types.OTHER); typeMap.put("int", Types.INTEGER); - typeMap.put("json", Types.VARCHAR); + typeMap.put("json", Types.OTHER); // typeMap.put("mbr", Types.???); typeMap.put("month_interval", Types.INTEGER); // typeMap.put("oid", Types.BIGINT); @@ -314,8 +314,8 @@ final public class MonetDriver implement typeMap.put("timetz", Types.TIME); // new in Java 8: Types.TIME_WITH_TIMEZONE (value 2013). Can't use it yet as we compile for java 7 typeMap.put("tinyint", Types.TINYINT); - typeMap.put("url", Types.VARCHAR); - typeMap.put("uuid", Types.VARCHAR); + typeMap.put("url", Types.OTHER); + typeMap.put("uuid", Types.OTHER); typeMap.put("varchar", Types.VARCHAR); typeMap.put("wrd", Types.BIGINT); } diff --git a/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java b/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java --- a/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java @@ -67,6 +67,20 @@ import java.util.Map; * @version 0.4 */ public class MonetPreparedStatement extends MonetStatement implements PreparedStatement { + + /* only parse the date patterns once, use multiple times */ + /** Format of a timestamp with RFC822 time zone */ + private static final SimpleDateFormat MTimestampZ = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); + /** Format of a timestamp */ + private static final SimpleDateFormat MTimestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + /** Format of a time with RFC822 time zone */ + private static final SimpleDateFormat MTimeZ = new SimpleDateFormat("HH:mm:ss.SSSZ"); + /** Format of a time */ + private static final SimpleDateFormat MTime = new SimpleDateFormat("HH:mm:ss.SSS"); + /** Format of a date used by mserver */ + private static final SimpleDateFormat MDate = new SimpleDateFormat("yyyy-MM-dd"); + + private final MonetConnection connection; private final String[] monetdbType; private final int[] javaType; private final int[] digits; @@ -77,28 +91,11 @@ public class MonetPreparedStatement exte private final int id; private final int size; private final int rscolcnt; - private final String[] values; - - private final MonetConnection connection; - - /* only parse the date patterns once, use multiple times */ - /** Format of a timestamp with RFC822 time zone */ - private final SimpleDateFormat mTimestampZ = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); - /** Format of a timestamp */ - private final SimpleDateFormat mTimestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - /** Format of a time with RFC822 time zone */ - private final SimpleDateFormat mTimeZ = new SimpleDateFormat("HH:mm:ss.SSSZ"); - /** Format of a time */ - private final SimpleDateFormat mTime = new SimpleDateFormat("HH:mm:ss.SSS"); - /** Format of a date used by mserver */ - private final SimpleDateFormat mDate = new SimpleDateFormat("yyyy-MM-dd"); /** - * MonetPreparedStatement constructor which checks the arguments for - * validity. A MonetPreparedStatement is backed by a - * {@link MonetStatement}, which deals with most of the required stuff of - * this class. + * MonetPreparedStatement constructor which checks the arguments for validity. A MonetPreparedStatement is backed + * by a {@link MonetStatement}, which deals with most of the required stuff of this class. * * @param connection the connection that created this Statement * @param resultSetType type of {@link ResultSet} to produce @@ -153,8 +150,7 @@ public class MonetPreparedStatement exte //== methods interface PreparedStatement /** - * Adds a set of parameters to this PreparedStatement object's batch - * of commands. + * Adds a set of parameters to this PreparedStatement object's batch of commands. * * @throws SQLException if a database access error occurs */ @@ -197,10 +193,9 @@ public class MonetPreparedStatement exte * getUpdateCount to retrieve the result; you must call * getMoreResults to move to any subsequent result(s). * - * @return true if the first result is a ResultSet object; false if the - * first result is an update count or there is no result - * @throws SQLException if a database access error occurs or an argument - * is supplied to this method + * @return true if the first result is a ResultSet object; false if the first result is an update count or there is + * no result + * @throws SQLException if a database access error occurs or an argument is supplied to this method */ @Override public boolean execute() throws SQLException { @@ -214,13 +209,10 @@ public class MonetPreparedStatement exte } /** - * Executes the SQL query in this PreparedStatement object and returns the - * ResultSet object generated by the query. + * Executes the SQL query in this PreparedStatement object and returns the ResultSet object generated by the query. * - * @return a ResultSet object that contains the data produced by the query; - * never null - * @throws SQLException if a database access error occurs or the SQL - * statement does not return a ResultSet object + * @return a ResultSet object that contains the data produced by the query never null + * @throws SQLException if a database access error occurs or the SQL statement does not return a ResultSet object */ @Override public ResultSet executeQuery() throws SQLException { @@ -243,8 +235,7 @@ public class MonetPreparedStatement exte * * @return either (1) the row count for INSERT, UPDATE, or DELETE * statements or (2) 0 for SQL statements that return nothing - * @throws SQLException if a database access error occurs or the SQL - * statement returns a ResultSet object + * @throws SQLException if a database access error occurs or the SQL statement returns a ResultSet object */ @Override public int executeUpdate() throws SQLException { @@ -275,6 +266,7 @@ public class MonetPreparedStatement exte } throw new SQLException("No such column with index: " + colnr, "M1M05"); } + /** * Returns the index (0..size-1) in the backing arrays for the given * parameter number or an SQLException when not found @@ -291,7 +283,6 @@ public class MonetPreparedStatement exte throw new SQLException("No such parameter with index: " + paramnr, "M1M05"); } - /* helper for the anonymous class inside getMetaData */ private abstract class rsmdw extends MonetWrapper implements ResultSetMetaData {} /** @@ -377,8 +368,7 @@ public class MonetPreparedStatement exte } /** - * Indicates whether the designated column can be used in a - * where clause. + * Indicates whether the designated column can be used in a where clause. * * Returning true for all here, even for CLOB, BLOB. * @@ -391,11 +381,9 @@ public class MonetPreparedStatement exte } /** - * Indicates whether the designated column is a cash value. - * From the MonetDB database perspective it is by definition - * unknown whether the value is a currency, because there are - * no currency datatypes such as MONEY. With this knowledge - * we can always return false here. + * Indicates whether the designated column is a cash value. From the MonetDB database perspective it is by + * definition unknown whether the value is a currency, because there are no currency datatypes such as + * MONEY. With this knowledge we can always return false here. * * @param column the first column is 1, the second is 2, ... * @return false @@ -406,8 +394,7 @@ public class MonetPreparedStatement exte } /** - * Indicates whether values in the designated column are signed - * numbers. + * Indicates whether values in the designated column are signed numbers. * Within MonetDB all numeric types (except oid and ptr) are signed. * * @param column the first column is 1, the second is 2, ... @@ -438,12 +425,10 @@ public class MonetPreparedStatement exte } /** - * Indicates the designated column's normal maximum width in - * characters. + * Indicates the designated column's normal maximum width in characters. * * @param column the first column is 1, the second is 2, ... - * @return the normal maximum number of characters allowed as the - * width of the designated column + * @return the normal maximum number of characters allowed as the width of the designated column * @throws SQLException if there is no such column */ @Override @@ -487,10 +472,8 @@ public class MonetPreparedStatement exte } /** - * Get the designated column's number of decimal digits. - * This method is currently very expensive as it needs to - * retrieve the information from the database using an SQL - * query. + * Get the designated column's number of decimal digits. This method is currently very expensive as it + * needs to retrieve the information from the database using an SQL query. * * @param column the first column is 1, the second is 2, ... * @return precision @@ -506,10 +489,8 @@ public class MonetPreparedStatement exte } /** - * Gets the designated column's number of digits to right of - * the decimal point. This method is currently very - * expensive as it needs to retrieve the information from - * the database using an SQL query. + * Gets the designated column's number of digits to right of the decimal point. This method is currently + * very expensive as it needs to retrieve the information from the database using an SQL query. * * @param column the first column is 1, the second is 2, ... * @return scale @@ -525,10 +506,8 @@ public class MonetPreparedStatement exte } /** - * Indicates the nullability of values in the designated - * column. This method is currently very expensive as it - * needs to retrieve the information from the database using - * an SQL query. + * Indicates the nullability of values in the designated column. This method is currently very expensive as + * it needs to retrieve the information from the database using an SQL query. * * @param column the first column is 1, the second is 2, ... * @return nullability @@ -544,8 +523,7 @@ public class MonetPreparedStatement exte * MonetDB does not support the catalog naming concept as in: catalog.schema.table naming scheme * * @param column the first column is 1, the second is 2, ... - * @return the name of the catalog for the table in which the given - * column appears or "" if not applicable + * @return the name of the catalog for the table in which the given column appears or "" if not applicable */ @Override public String getCatalogName(int column) throws SQLException { @@ -553,9 +531,8 @@ public class MonetPreparedStatement exte } /** - * Indicates whether the designated column is definitely not - * writable. MonetDB does not support cursor updates, so - * nothing is writable. + * Indicates whether the designated column is definitely not writable. MonetDB does not support cursor + * updates, so nothing is writable. * * @param column the first column is 1, the second is 2, ... * @return true if so; false otherwise @@ -566,8 +543,7 @@ public class MonetPreparedStatement exte } /** - * Indicates whether it is possible for a write on the - * designated column to succeed. + * Indicates whether it is possible for a write on the designated column to succeed. * * @param column the first column is 1, the second is 2, ... * @return true if so; false otherwise @@ -578,8 +554,7 @@ public class MonetPreparedStatement exte } /** - * Indicates whether a write on the designated column will - * definitely succeed. + * Indicates whether a write on the designated column will definitely succeed. * * @param column the first column is 1, the second is 2, ... * @return true if so; false otherwise @@ -590,18 +565,14 @@ public class MonetPreparedStatement exte } /** - * Returns the fully-qualified name of the Java class whose - * instances are manufactured if the method - * ResultSet.getObject is called to retrieve a value from - * the column. ResultSet.getObject may return a subclass of - * the class returned by this method. + * Returns the fully-qualified name of the Java class whose instances are manufactured if the method + * ResultSet.getObject is called to retrieve a value from the column. ResultSet.getObject may return a + * subclass of the class returned by this method. * * @param column the first column is 1, the second is 2, ... - * @return the fully-qualified name of the class in the Java - * programming language that would be used by the method - * ResultSet.getObject to retrieve the value in the - * specified column. This is the class name used for custom - * mapping. + * @return the fully-qualified name of the class in the Java programming language that would be used by the + * method ResultSet.getObject to retrieve the value in the specified column. This is the class name used + * for custom mapping. * @throws SQLException if there is no such column */ @Override @@ -610,9 +581,8 @@ public class MonetPreparedStatement exte } /** - * Gets the designated column's suggested title for use in - * printouts and displays. This is currently equal to - * getColumnName(). + * Gets the designated column's suggested title for use in printouts and displays. This is currently equal + * to getColumnName(). * * @param column the first column is 1, the second is 2, ... * @return the suggested column title @@ -659,9 +629,8 @@ public class MonetPreparedStatement exte * Retrieves the designated column's database-specific type name. * * @param column the first column is 1, the second is 2, ... - * @return type name used by the database. If the column type is a - * user-defined type, then a fully-qualified type name is - * returned. + * @return type name used by the database. If the column type is a user-defined type, then a + * fully-qualified type name is returned. * @throws SQLException if there is no such column */ @Override @@ -678,20 +647,17 @@ public class MonetPreparedStatement exte /* helper class for the anonymous class in getParameterMetaData */ private abstract class pmdw extends MonetWrapper implements ParameterMetaData {} /** - * Retrieves the number, types and properties of this - * PreparedStatement object's parameters. + * Retrieves the number, types and properties of this PreparedStatement object's parameters. * - * @return a ParameterMetaData object that contains information - * about the number, types and properties of this - * PreparedStatement object's parameters + * @return a ParameterMetaData object that contains information about the number, types and properties of this + * PreparedStatement object's parameters * @throws SQLException if a database access error occurs */ @Override public ParameterMetaData getParameterMetaData() throws SQLException { return new pmdw() { /** - * Retrieves the number of parameters in the - * PreparedStatement object for which this ParameterMetaData + * Retrieves the number of parameters in the PreparedStatement object for which this ParameterMetaData * object contains information. * * @return the number of parameters @@ -710,16 +676,13 @@ public class MonetPreparedStatement exte } /** - * Retrieves whether null values are allowed in the - * designated parameter. + * Retrieves whether null values are allowed in the designated parameter. * * This is currently always unknown for MonetDB/SQL. * * @param param the first parameter is 1, the second is 2, ... - * @return the nullability status of the given parameter; - * one of ParameterMetaData.parameterNoNulls, - * ParameterMetaData.parameterNullable, or - * ParameterMetaData.parameterNullableUnknown + * @return the nullability status of the given parameter; one of ParameterMetaData.parameterNoNulls, + * ParameterMetaData.parameterNullable, or ParameterMetaData.parameterNullableUnknown * @throws SQLException if a database access error occurs */ @Override @@ -728,8 +691,7 @@ public class MonetPreparedStatement exte } /** - * Retrieves whether values for the designated parameter can - * be signed numbers. + * Retrieves whether values for the designated parameter can be signed numbers. * * @param param the first parameter is 1, the second is 2, ... * @return true if so; false otherwise @@ -737,7 +699,7 @@ public class MonetPreparedStatement exte */ @Override public boolean isSigned(int param) throws SQLException { - // we can hardcode this, based on the colum type + // we can hardcode this, based on the column type switch (getParameterType(param)) { case Types.NUMERIC: case Types.DECIMAL: @@ -1364,8 +1326,8 @@ public class MonetPreparedStatement exte if (cal == null) { setValue(parameterIndex, "date '" + x.toString() + "'"); } else { - mDate.setTimeZone(cal.getTimeZone()); - setValue(parameterIndex, "date '" + mDate.format(x) + "'"); + MDate.setTimeZone(cal.getTimeZone()); + setValue(parameterIndex, "date '" + MDate.format(x) + "'"); } } @@ -2238,7 +2200,7 @@ public class MonetPreparedStatement exte if (hasTimeZone) { // timezone shouldn't matter, since the server is timezone // aware in this case - String RFC822 = mTimeZ.format(x); + String RFC822 = MTimeZ.format(x); setValue(index, "timetz '" + RFC822.substring(0, 15) + ":" + RFC822.substring(15) + "'"); } else { // server is not timezone aware for this field, and no @@ -2248,8 +2210,8 @@ public class MonetPreparedStatement exte if (cal == null) { setValue(index, "time '" + x.toString() + "'"); } else { - mTime.setTimeZone(cal.getTimeZone()); - setValue(index, "time '" + mTime.format(x) + "'"); + MTime.setTimeZone(cal.getTimeZone()); + setValue(index, "time '" + MTime.format(x) + "'"); } } } @@ -2295,7 +2257,7 @@ public class MonetPreparedStatement exte if (hasTimeZone) { // timezone shouldn't matter, since the server is timezone // aware in this case - String RFC822 = mTimestampZ.format(x); + String RFC822 = MTimestampZ.format(x); setValue(index, "timestamptz '" + RFC822.substring(0, 26) + ":" + RFC822.substring(26) + "'"); } else { // server is not timezone aware for this field, and no @@ -2305,8 +2267,8 @@ public class MonetPreparedStatement exte if (cal == null) { setValue(index, "timestamp '" + x.toString() + "'"); } else { - mTimestamp.setTimeZone(cal.getTimeZone()); - setValue(index, "timestamp '" + mTimestamp.format(x) + "'"); + MTimestamp.setTimeZone(cal.getTimeZone()); + setValue(index, "timestamp '" + MTimestamp.format(x) + "'"); } } } @@ -2377,8 +2339,7 @@ public class MonetPreparedStatement exte } /** - * Call close to release the server-sided handle for this - * PreparedStatement. + * Call close to release the server-sided handle for this PreparedStatement. */ @Override protected void finalize() { @@ -2388,9 +2349,8 @@ public class MonetPreparedStatement exte //== end methods interface PreparedStatement /** - * Sets the given index with the supplied value. If the given index is - * out of bounds, and SQLException is thrown. The given value should - * never be null. + * Sets the given index with the supplied value. If the given index is out of bounds, and SQLException is thrown. + * The given value should never be null. * * @param index the parameter index * @param val the exact String representation to set diff --git a/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java b/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java --- a/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java @@ -10,12 +10,10 @@ package nl.cwi.monetdb.jdbc; import nl.cwi.monetdb.jdbc.types.INET; import nl.cwi.monetdb.jdbc.types.URL; +import nl.cwi.monetdb.mcl.responses.DataBlockResponse; import nl.cwi.monetdb.mcl.responses.ResultSetResponse; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Reader; -import java.io.StringReader; +import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; @@ -92,7 +90,7 @@ public class MonetResultSet extends Mone /** Just a dummy variable to keep store the fetchsize set. */ private int fetchSize; /** The current row's values */ - Object[] values; + DataBlockResponse currentBlock; /** * Main constructor backed by the given Header. @@ -120,7 +118,6 @@ public class MonetResultSet extends Mone this.columns = header.getNames(); this.types = header.getTypes(); this.JdbcSQLTypes = header.getJdbcSQLTypes(); - this.values = header.getLine(this.curRow); } /** @@ -156,6 +153,11 @@ public class MonetResultSet extends Mone this.JdbcSQLTypes = JdbcSQLTypes; } + private boolean setLastNullValue(int columnIndex) { + this.lastReadWasNull = currentBlock.checkValueIsNull(columnIndex); + return this.lastReadWasNull; + } + //== methods of interface ResultSet // Chapter 14.2.2 Sun JDBC 3.0 Specification @@ -201,19 +203,17 @@ public class MonetResultSet extends Mone } else if (row > tupleCount + 1) { row = tupleCount + 1; // after last } - - this.values = header.getLine(row - 1); // store it this.curRow = row; - return this.values != null; + this.currentBlock = header.getDataBlockCorrespondingToLine(row - 1); + return this.curRow <= this.tupleCount; } /** * Moves the cursor to the end of this ResultSet object, just after the last row. This method has no effect if the * result set contains no rows. * - * @throws SQLException if a database access error occurs or the result set - * type is TYPE_FORWARD_ONLY + * @throws SQLException if a database access error occurs or the result set type is TYPE_FORWARD_ONLY */ @Override public void afterLast() throws SQLException { @@ -224,8 +224,7 @@ public class MonetResultSet extends Mone * Moves the cursor to the front of this ResultSet object, just before the first row. This method has no effect * if the result set contains no rows. * - * @throws SQLException if a database access error occurs or the result set - * type is TYPE_FORWARD_ONLY + * @throws SQLException if a database access error occurs or the result set type is TYPE_FORWARD_ONLY */ @Override public void beforeFirst() throws SQLException { @@ -407,13 +406,10 @@ public class MonetResultSet extends Mone @Override public Reader getCharacterStream(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return new StringReader((String) val); + return new StringReader(currentBlock.getValueAsString(columnIndex - 1)); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -485,13 +481,10 @@ public class MonetResultSet extends Mone @Override public Blob getBlob(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return (MonetBlob) val; + return (MonetBlob) currentBlock.getObjectValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -528,13 +521,10 @@ public class MonetResultSet extends Mone @Override public Clob getClob(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return (MonetClob) val; + return (MonetClob) currentBlock.getObjectValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -605,13 +595,10 @@ public class MonetResultSet extends Mone @Override public BigDecimal getBigDecimal(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return (BigDecimal) val; + return (BigDecimal) currentBlock.getObjectValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -633,16 +620,12 @@ public class MonetResultSet extends Mone @Deprecated public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - - BigDecimal bd = (BigDecimal) val; - bd.setScale(scale); - return bd; + BigDecimal val = (BigDecimal) currentBlock.getObjectValue(columnIndex - 1); + val.setScale(scale); + return val; } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -693,24 +676,21 @@ public class MonetResultSet extends Mone @Override public boolean getBoolean(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; - return false; // if the value is SQL NULL, the value returned is false + if(setLastNullValue(columnIndex - 1)) { + return false; // if the value is SQL NULL, the value returned is false } - lastReadWasNull = false; // match type specific values switch (JdbcSQLTypes[columnIndex - 1]) { case Types.BOOLEAN: - return (Boolean) val; + return currentBlock.getBooleanValue(columnIndex - 1); case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness case Types.CLOB: - String other = (String) val; - if ("false".equalsIgnoreCase(other) || "0".equals(val)) + String val = currentBlock.getValueAsString(columnIndex - 1); + if ("false".equalsIgnoreCase(val) || "0".equals(val)) return false; - if ("true".equalsIgnoreCase(other) || "1".equals(val)) + if ("true".equalsIgnoreCase(val) || "1".equals(val)) return true; throw newSQLInvalidColumnIndexException(columnIndex); case Types.BIT: // MonetDB doesn't use type BinaryDigit, it's here for completeness @@ -764,13 +744,10 @@ public class MonetResultSet extends Mone @Override public byte getByte(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; - return (byte) 0; + if(setLastNullValue(columnIndex - 1)) { + return 0; } - lastReadWasNull = false; - return (Byte) val; + return currentBlock.getByteValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -804,22 +781,18 @@ public class MonetResultSet extends Mone @Override public byte[] getBytes(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - // According to Table B-6, getBytes() only operates on BINARY types switch (JdbcSQLTypes[columnIndex - 1]) { case Types.BLOB: - return ((MonetBlob) val).getBuffer(); + return ((MonetBlob) currentBlock.getObjectValue(columnIndex - 1)).getBuffer(); case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: // unpack the HEX (BLOB) notation to real bytes - return (byte[]) val; + return (byte[]) currentBlock.getObjectValue(columnIndex - 1); default: throw new SQLException("Cannot operate on " + types[columnIndex - 1] + " type", "M1M05"); } @@ -899,13 +872,10 @@ public class MonetResultSet extends Mone @Override public double getDouble(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; - return 0; + if(setLastNullValue(columnIndex - 1)) { + return 0.0d; } - lastReadWasNull = false; - return (Double) val; + return currentBlock.getDoubleValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -1019,13 +989,10 @@ public class MonetResultSet extends Mone @Override public float getFloat(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; - return 0; + if(setLastNullValue(columnIndex - 1)) { + return 0.0f; } - lastReadWasNull = false; - return (Float) val; + return currentBlock.getFloatValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -1056,15 +1023,11 @@ public class MonetResultSet extends Mone */ @Override public int getInt(int columnIndex) throws SQLException { - Object val; try { - val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return 0; } - lastReadWasNull = false; - return (Integer) val; + return currentBlock.getIntValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -1095,15 +1058,13 @@ public class MonetResultSet extends Mone */ @Override public long getLong(int columnIndex) throws SQLException { - Object val; try { - val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return 0; } - lastReadWasNull = false; - return (Long) val; + return currentBlock.getLongValue(columnIndex - 1); + } catch (ClassCastException ex) { + throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } @@ -1486,7 +1447,8 @@ public class MonetResultSet extends Mone * an SQL query. * * @param column the first column is 1, the second is 2, ... - * @return the nullability status of the given column; one of columnNoNulls, columnNullable or columnNullableUnknown + * @return the nullability status of the given column; one of columnNoNulls, columnNullable or + * columnNullableUnknown * @throws SQLException if a database access error occurs */ @Override @@ -1680,17 +1642,13 @@ public class MonetResultSet extends Mone */ @Override public Object getObject(int columnIndex) throws SQLException { - // Many generic JDBC programs use getObject(colnr) to retrieve value objects from a resultset - // For speed the implementation should be as fast as possible, so avoid method calls (by inlining code) where possible + // Many generic JDBC programs use getObject(colnr) to retrieve value objects from a resultset. For speed the + // implementation should be as fast as possible, so avoid method calls (by inlining code) where possible final int JdbcType; - final Object val; try { - val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; JdbcType = JdbcSQLTypes[columnIndex - 1]; } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); @@ -1698,6 +1656,8 @@ public class MonetResultSet extends Mone switch(JdbcType) { case Types.BIT: // MonetDB doesn't use type BInary digiT, it's here for completeness + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.BOOLEAN: case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: @@ -1707,22 +1667,32 @@ public class MonetResultSet extends Mone case Types.REAL: case Types.DECIMAL: case Types.NUMERIC: - case Types.BOOLEAN: case Types.CHAR: - case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.VARCHAR: + case Types.BLOB: case Types.CLOB: - case Types.BLOB: - return val; - case Types.VARCHAR: { + return currentBlock.getValueAsObject(columnIndex - 1); + case Types.DATE: + return getDate(columnIndex); + case Types.TIME: + return getTime(columnIndex); + case Types.TIMESTAMP: + return getTimestamp(columnIndex); + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: // MonetDB doesn't use type LONGVARBINARY, it's here for completeness + return getBytes(columnIndex); + case Types.OTHER: { // The MonetDB types: inet, json, url and uuid are all mapped to Types.VARCHAR in MonetDriver.typeMap // For these MonetDB types (except json, see comments below) we try to create objects of the corresponding class. String MonetDBType = types[columnIndex - 1]; + String val = currentBlock.getValueAsString(columnIndex - 1); switch (MonetDBType.length()) { case 3: if ("url".equals(MonetDBType)) { try { URL url_obj = new URL(); - url_obj.fromString((String) val); + url_obj.fromString(val); return url_obj; } catch (Exception exc) { // ignore exception and just return the val String object @@ -1734,7 +1704,7 @@ public class MonetResultSet extends Mone if ("inet".equals(MonetDBType)) { try { INET inet_obj = new INET(); - inet_obj.fromString((String) val); + inet_obj.fromString(val); return inet_obj; } catch (Exception exc) { // ignore exception and just return the val String object @@ -1743,13 +1713,12 @@ public class MonetResultSet extends Mone } else if ("uuid".equals(MonetDBType)) { try { - return UUID.fromString((String) val); + return UUID.fromString(val); } catch (IllegalArgumentException exc) { // ignore exception and just return the val String object return val; } -// } else -// if ("json".equals(MonetDBType)) { +// } else if ("json".equals(MonetDBType)) { // There is no support for JSON in standard java class libraries. // Possibly we could use org.json.simple.JSONObject or other/faster libs // javax.json.Json is not released yet (see https://json-processing-spec.java.net/) @@ -1762,17 +1731,6 @@ public class MonetResultSet extends Mone } return val; } - case Types.DATE: - return getDate(columnIndex); - case Types.TIME: - return getTime(columnIndex); - case Types.TIMESTAMP: - return getTimestamp(columnIndex); - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: // MonetDB doesn't use type LONGVARBINARY, it's here for completeness - return getBytes(columnIndex); - case Types.OTHER: default: // When we get here the column type is a non-standard JDBC SQL type, possibly a User Defined Type. // Just call getObject(int, Map) for those rare cases. @@ -1821,15 +1779,11 @@ public class MonetResultSet extends Mone @Override @SuppressWarnings("unchecked") public Object getObject(int columnIndex, Map<String,Class<?>> map) throws SQLException { - final Object val; final String MonetDBtype; try { - val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; MonetDBtype = types[columnIndex - 1]; } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); @@ -1845,7 +1799,7 @@ public class MonetResultSet extends Mone } if (type == null || type == String.class) { - return val; + return currentBlock.getValueAsString(columnIndex - 1); } else if (type == BigDecimal.class) { return getBigDecimal(columnIndex); } else if (type == Boolean.class) { @@ -2022,7 +1976,7 @@ public class MonetResultSet extends Mone x.readSQL(input, MonetDBtype); return x; } else { - return val; + return currentBlock.getObjectValue(columnIndex - 1); } } @@ -2234,13 +2188,10 @@ public class MonetResultSet extends Mone @Override public short getShort(int columnIndex) throws SQLException { try { - Object val = this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return 0; } - lastReadWasNull = false; - return (Short) val; + return currentBlock.getShortValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -2285,13 +2236,10 @@ public class MonetResultSet extends Mone @Override public String getString(int columnIndex) throws SQLException { try { - String val = (String) this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return val; + return currentBlock.getValueAsString(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -2403,13 +2351,10 @@ public class MonetResultSet extends Mone @Override public Date getDate(int columnIndex, Calendar cal) throws SQLException { try { - Date val = (Date) this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return val; + return (Date) currentBlock.getObjectValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -2479,13 +2424,10 @@ public class MonetResultSet extends Mone if (cal == null) throw new IllegalArgumentException("No Calendar object given!"); try { - Time val = (Time) this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return val; + return (Time) currentBlock.getObjectValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -2555,13 +2497,10 @@ public class MonetResultSet extends Mone if (cal == null) throw new IllegalArgumentException("No Calendar object given!"); try { - Timestamp val = (Timestamp) this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; - return val; + return (Timestamp) currentBlock.getObjectValue(columnIndex - 1); } catch (ClassCastException ex) { throw new SQLException(ex.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -2624,14 +2563,11 @@ public class MonetResultSet extends Mone @Override public java.net.URL getURL(int columnIndex) throws SQLException { try { - String val = (String) this.values[columnIndex - 1]; - if (val == null) { - lastReadWasNull = true; + if(setLastNullValue(columnIndex - 1)) { return null; } - lastReadWasNull = false; try { - return new java.net.URL(val); + return new java.net.URL(currentBlock.getValueAsString(columnIndex - 1)); } catch (MalformedURLException e) { throw new SQLException(e.getMessage(), "M1M05"); } diff --git a/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java b/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java --- a/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java @@ -678,13 +678,13 @@ public class MonetStatement extends Mone if (header instanceof UpdateResponse) { String lastid = ((UpdateResponse)header).getLastid(); if (lastid.equals("-1")) { - results = new String[0][1]; + results = new String[1][1]; } else { results = new String[1][1]; results[0][0] = lastid; } } else { - results = new String[0][1]; + results = new String[1][1]; } try { @@ -1211,17 +1211,17 @@ final class MonetVirtualResultSet extend throws IllegalArgumentException { super(statement, columns, types, jdbcTypes, results.length); this.results = results; - closed = false; + this.closed = false; + this.currentBlock.setData(results); } /** - * This method is overridden in order to let it use the results array - * instead of the cache in the Statement object that created it. + * This method is overridden in order to let it use the results array instead of the cache in the Statement object + * that created it. * - * @param row the number of the row to which the cursor should move. A - * positive number indicates the row number counting from the - * beginning of the result set; a negative number indicates the row - * number counting from the end of the result set + * @param row the number of the row to which the cursor should move. A positive number indicates the row number + * counting from the beginning of the result set; a negative number indicates the row number counting from the end + * of the result set * @return true if the cursor is on the result set; false otherwise * @throws SQLException if a database error occurs */ @@ -1245,7 +1245,9 @@ final class MonetVirtualResultSet extend // see if we have the row if (row < 1 || row > tupleCount) return false; - System.arraycopy(this.results[row - 1], 0, this.values, 0, this.results[row - 1].length); + String[] values = (String[]) this.currentBlock.getData()[0]; + + System.arraycopy(this.results[row - 1], 0, values, 0, this.results[row - 1].length); return true; } diff --git a/src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBConnectionFactory.java b/src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBConnectionFactory.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBConnectionFactory.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/MonetDBConnectionFactory.java @@ -3,8 +3,8 @@ package nl.cwi.monetdb.mcl.connection; import nl.cwi.monetdb.jdbc.MonetConnection; import nl.cwi.monetdb.jdbc.MonetDriver; import nl.cwi.monetdb.mcl.connection.embedded.EmbeddedConnection; -import nl.cwi.monetdb.mcl.connection.socket.MapiConnection; -import nl.cwi.monetdb.mcl.connection.socket.MapiLanguage; +import nl.cwi.monetdb.mcl.connection.mapi.MapiConnection; +import nl.cwi.monetdb.mcl.connection.mapi.MapiLanguage; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import java.io.File; diff --git a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/AbstractSocket.java b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java rename from src/main/java/nl/cwi/monetdb/mcl/connection/socket/AbstractSocket.java rename to src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/AbstractSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java @@ -1,4 +1,4 @@ -package nl.cwi.monetdb.mcl.connection.socket; +package nl.cwi.monetdb.mcl.connection.mapi; import java.io.Closeable; import java.io.IOException; @@ -85,18 +85,23 @@ public abstract class AbstractSocket imp public int readLine(StringBuilder builder) throws IOException { builder.setLength(0); boolean found = false; + char[] array = this.stringsDecoded.array(); + int position = this.stringsDecoded.position(); while(!found) { if(!this.stringsDecoded.hasRemaining()) { this.readToBuffer(); + array = this.stringsDecoded.array(); + position = 0; } - char c = this.stringsDecoded.get(); + char c = array[position++]; if(c == '\n') { found = true; } else { builder.append(c); } } + this.stringsDecoded.position(position); return builder.length(); } diff --git a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/MapiConnection.java b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java rename from src/main/java/nl/cwi/monetdb/mcl/connection/socket/MapiConnection.java rename to src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/MapiConnection.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java @@ -1,4 +1,4 @@ -package nl.cwi.monetdb.mcl.connection.socket; +package nl.cwi.monetdb.mcl.connection.mapi; import nl.cwi.monetdb.jdbc.MonetConnection; import nl.cwi.monetdb.mcl.connection.ChannelSecurity; diff --git a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/MapiLanguage.java b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiLanguage.java rename from src/main/java/nl/cwi/monetdb/mcl/connection/socket/MapiLanguage.java rename to src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiLanguage.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/MapiLanguage.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiLanguage.java @@ -1,4 +1,4 @@ -package nl.cwi.monetdb.mcl.connection.socket; +package nl.cwi.monetdb.mcl.connection.mapi; import nl.cwi.monetdb.mcl.connection.IMonetDBLanguage; diff --git a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/OldMapiSocket.java b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/OldMapiSocket.java rename from src/main/java/nl/cwi/monetdb/mcl/connection/socket/OldMapiSocket.java rename to src/main/java/nl/cwi/monetdb/mcl/connection/mapi/OldMapiSocket.java --- a/src/main/java/nl/cwi/monetdb/mcl/connection/socket/OldMapiSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/OldMapiSocket.java @@ -1,4 +1,4 @@ -package nl.cwi.monetdb.mcl.connection.socket; +package nl.cwi.monetdb.mcl.connection.mapi; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java b/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java --- a/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/AbstractProtocol.java @@ -46,7 +46,8 @@ public abstract class AbstractProtocol<T public abstract TableResultHeaders getNextTableHeader(Object line, String[] stringValues, int[] intValues) throws ProtocolException; - public abstract int parseTupleLine(Object line, Object[] values, int[] typesMap) throws ProtocolException; + public abstract int parseTupleLine(int lineNumber, Object line, int[] typesMap, Object[] values, boolean[] nulls) + throws ProtocolException; public abstract String getRemainingStringLine(int startIndex); diff --git a/src/main/java/nl/cwi/monetdb/mcl/protocol/embedded/EmbeddedProtocol.java b/src/main/java/nl/cwi/monetdb/mcl/protocol/embedded/EmbeddedProtocol.java --- a/src/main/java/nl/cwi/monetdb/mcl/protocol/embedded/EmbeddedProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/embedded/EmbeddedProtocol.java @@ -72,7 +72,7 @@ public class EmbeddedProtocol extends Ab } @Override - public int parseTupleLine(Object line, Object[] values, int[] typesMap) throws ProtocolException { + public int parseTupleLine(int lineNumber, Object line, int[] typesMap, Object[] values, boolean[] nulls) throws ProtocolException { return 0; } diff --git a/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java b/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java --- a/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/newmapi/NewMapiProtocol.java @@ -61,7 +61,7 @@ public class NewMapiProtocol extends Abs } @Override - public int parseTupleLine(Object line, Object[] values, int[] typesMap) throws ProtocolException { + public int parseTupleLine(int lineNumber, Object line, int[] typesMap, Object[] values, boolean[] nulls) throws ProtocolException { return 0; } diff --git a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java --- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiProtocol.java @@ -1,7 +1,7 @@ package nl.cwi.monetdb.mcl.protocol.oldmapi; import nl.cwi.monetdb.jdbc.MonetConnection; -import nl.cwi.monetdb.mcl.connection.socket.OldMapiSocket; +import nl.cwi.monetdb.mcl.connection.mapi.OldMapiSocket; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.protocol.AbstractProtocol; import nl.cwi.monetdb.mcl.protocol.ServerResponses; @@ -128,9 +128,10 @@ public class OldMapiProtocol extends Abs } @Override - public int parseTupleLine(Object line, Object[] values, int[] typesMap) throws ProtocolException { - return OldMapiTupleLineParser.OldMapiParseTupleLine((StringBuilder) line, values, this.tupleLineBuilder, - typesMap); + public int parseTupleLine(int lineNumber, Object line, int[] typesMap, Object[] data, boolean[] nulls) + throws ProtocolException { + return OldMapiTupleLineParser.OldMapiParseTupleLine(lineNumber, (StringBuilder) line, + this.tupleLineBuilder, typesMap, data, nulls); } @Override diff --git a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java --- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTableHeaderParser.java @@ -90,10 +90,10 @@ final class OldMapiTableHeaderParser { if (builder.charAt(i) == ',' && builder.charAt(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 + // 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'; } else { diff --git a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java --- a/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java +++ b/src/main/java/nl/cwi/monetdb/mcl/protocol/oldmapi/OldMapiTupleLineParser.java @@ -17,16 +17,17 @@ import java.text.SimpleDateFormat; */ final class OldMapiTupleLineParser { - static int OldMapiParseTupleLine(StringBuilder line, Object[] values, StringBuilder helper, int[] jDBCTypesMap) throws ProtocolException { + static int OldMapiParseTupleLine(int lineNumber, StringBuilder line, StringBuilder helper, int[] typesMap, + Object[] values, boolean[] nulls) throws ProtocolException { int len = line.length(); // first detect whether this is a single value line (=) or a real tuple ([) if (line.charAt(0) == '=') { - if (values.length != 1) { - throw new ProtocolException(values.length + " columns expected, but only single value found"); + if (typesMap.length != 1) { + throw new ProtocolException(typesMap.length + " columns expected, but only single value found"); } // return the whole string but the leading = - values[0] = line.substring(1); + OldMapiStringToJavaObjectConverter(line.substring(1), lineNumber, values[0], typesMap[0]); return 1; } @@ -119,11 +120,14 @@ final class OldMapiTupleLineParser { } // put the unescaped string in the right place - values[column] = OldMapiStringToJavaObjectConverter(helper.toString(), jDBCTypesMap[column]); + OldMapiStringToJavaObjectConverter(helper.toString(), lineNumber, values[column], typesMap[column]); + nulls[column] = false; } else if ((i - 1) - cursor == 4 && line.indexOf("NULL", cursor) == cursor) { - values[column] = null; + SetNullValue(lineNumber, values[column], typesMap[column]); + nulls[column] = true; } else { - values[column] = OldMapiStringToJavaObjectConverter(line.substring(cursor, i - 1), jDBCTypesMap[column]); + OldMapiStringToJavaObjectConverter(line.substring(cursor, i - 1), lineNumber, values[column], typesMap[column]); + nulls[column] = false; } column++; cursor = i + 1; @@ -134,7 +138,7 @@ final class OldMapiTupleLineParser { } } // check if this result is of the size we expected it to be - if (column != values.length) + if (column != typesMap.length) throw new ProtocolException("illegal result length: " + column + "\nlast read: " + (column > 0 ? values[column - 1] : "<none>")); return column; } @@ -154,56 +158,101 @@ final class OldMapiTupleLineParser { return res; } - private static Object OldMapiStringToJavaObjectConverter(String toParse, int jDBCMapping) throws ProtocolException { + private static void OldMapiStringToJavaObjectConverter(String toParse, int lineNumber, Object columnArray, + int jDBCMapping) throws ProtocolException { switch (jDBCMapping) { + case Types.BOOLEAN: + ((boolean[]) columnArray)[lineNumber] = Boolean.parseBoolean(toParse); + break; + case Types.TINYINT: + ((byte[]) columnArray)[lineNumber] = Byte.parseByte(toParse); + break; + case Types.SMALLINT: + ((short[]) columnArray)[lineNumber] = Short.parseShort(toParse); + break; + case Types.INTEGER: + ((int[]) columnArray)[lineNumber] = Integer.parseInt(toParse); + break; case Types.BIGINT: - return Long.parseLong(toParse); - case Types.BLOB: - return new MonetBlob(BinaryBlobConverter(toParse)); - case Types.BINARY: - return BinaryBlobConverter(toParse); - case Types.BOOLEAN: - return Boolean.parseBoolean(toParse); + ((long[]) columnArray)[lineNumber] = Long.parseLong(toParse); + break; + case Types.REAL: + ((float[]) columnArray)[lineNumber] = Float.parseFloat(toParse); + break; + case Types.DOUBLE: + ((double[]) columnArray)[lineNumber] = Double.parseDouble(toParse); + break; + case Types.DECIMAL: + ((Object[]) columnArray)[lineNumber] = new BigDecimal(toParse); + break; + case Types.NUMERIC: + ((Object[]) columnArray)[lineNumber] = new BigInteger(toParse); + break; case Types.CHAR: - return toParse; - case Types.CLOB: - return new MonetClob(toParse); + case Types.VARCHAR: + case Types.OTHER: + ((Object[]) columnArray)[lineNumber] = toParse; + break; case Types.DATE: try { - return DateParser.parse(toParse); + ((Object[]) columnArray)[lineNumber] = DateParser.parse(toParse); + } catch (ParseException e) { + throw new ProtocolException(e.getMessage()); + } + break; + case Types.TIME: + try { + ((Object[]) columnArray)[lineNumber] = TimeParser.parse(toParse); } catch (ParseException e) { throw new ProtocolException(e.getMessage()); } - case Types.DECIMAL: - return new BigDecimal(toParse); - case Types.DOUBLE: - return Double.parseDouble(toParse); - case Types.NUMERIC: - return new BigInteger(toParse); - case Types.INTEGER: - return Integer.parseInt(toParse); - case Types.REAL: - return Float.parseFloat(toParse); - case Types.SMALLINT: - return Short.parseShort(toParse); - case Types.TIME: + break; + case Types.TIMESTAMP: try { - return TimeParser.parse(toParse); + ((Object[]) columnArray)[lineNumber] = TimestampParser.parse(toParse); } catch (ParseException e) { throw new ProtocolException(e.getMessage()); } - case Types.TIMESTAMP: - try { - return TimestampParser.parse(toParse); - } catch (ParseException e) { - throw new ProtocolException(e.getMessage()); - } + break; + case Types.CLOB: + ((Object[]) columnArray)[lineNumber] = new MonetClob(toParse); + break; + case Types.BLOB: + ((Object[]) columnArray)[lineNumber] = new MonetBlob(BinaryBlobConverter(toParse)); + break; + case Types.BINARY: + ((Object[]) columnArray)[lineNumber] = BinaryBlobConverter(toParse); + break; + default: + throw new ProtocolException("Unknown type!"); + } + } + + private static void SetNullValue(int lineNumber, Object columnArray, int jDBCMapping) throws ProtocolException { + switch (jDBCMapping) { + case Types.BOOLEAN: + ((boolean[]) columnArray)[lineNumber] = false; + break; case Types.TINYINT: - return Byte.parseByte(toParse); - case Types.VARCHAR: - return toParse; + ((byte[]) columnArray)[lineNumber] = Byte.MIN_VALUE; + break; + case Types.SMALLINT: + ((short[]) columnArray)[lineNumber] = Short.MIN_VALUE; + break; + case Types.INTEGER: + ((int[]) columnArray)[lineNumber] = Integer.MIN_VALUE; + break; + case Types.BIGINT: + ((long[]) columnArray)[lineNumber] = Long.MIN_VALUE; + break; + case Types.REAL: + ((float[]) columnArray)[lineNumber] = Float.MIN_VALUE; + break; + case Types.DOUBLE: + ((double[]) columnArray)[lineNumber] = Double.MIN_VALUE; + break; default: - return null; + ((Object[]) columnArray)[lineNumber] = null; } } } diff --git a/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java b/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java --- a/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/DataBlockResponse.java @@ -5,6 +5,7 @@ import nl.cwi.monetdb.mcl.protocol.Proto import nl.cwi.monetdb.mcl.protocol.ServerResponses; import java.sql.SQLException; +import java.sql.Types; /** * The DataBlockResponse is tabular data belonging to a @@ -26,15 +27,17 @@ import java.sql.SQLException; public class DataBlockResponse implements IIncompleteResponse { /** The array to keep the data in */ - private final Object[][] data; + private Object[] data; /** The counter which keeps the current position in the data array */ private int pos; - /** Whether we can discard lines as soon as we have read them */ - private boolean forwardOnly; /** The connection protocol to parse the tuple lines */ private final AbstractProtocol<?> protocol; /** The JdbcSQLTypes mapping */ private final int[] jdbcSQLTypes; + /** A mapping of null values of the current Row */ + private boolean[][] nullMappings; + /** A 'pointer' to the current line */ + private int blockLine; /** * Constructs a DataBlockResponse object. @@ -45,8 +48,8 @@ public class DataBlockResponse implement */ DataBlockResponse(int rowcount, int columncount, boolean forward, AbstractProtocol<?> protocol, int[] JdbcSQLTypes) { this.pos = -1; - this.forwardOnly = forward; - this.data = new Object[rowcount][columncount]; + this.data = new Object[columncount]; + this.nullMappings = new boolean[rowcount][columncount]; this.protocol = protocol; this.jdbcSQLTypes = JdbcSQLTypes; } @@ -63,9 +66,41 @@ public class DataBlockResponse implement public void addLine(ServerResponses response, Object line) throws ProtocolException { if (response != ServerResponses.RESULT) throw new ProtocolException("protocol violation: unexpected line in data block: " + line.toString()); + + if(this.pos == -1) { //if it's the first line, initialize the matrix + int numberOfColumns = this.data.length, numberOfRows = this.nullMappings.length; + for (int i = 0 ; i < numberOfColumns ; i++) { + switch (this.jdbcSQLTypes[i]) { + case Types.BOOLEAN: + this.data[i] = new boolean[numberOfRows]; + break; + case Types.TINYINT: + this.data[i] = new byte[numberOfRows]; + break; + case Types.SMALLINT: + this.data[i] = new short[numberOfRows]; + break; + case Types.INTEGER: + this.data[i] = new int[numberOfRows]; + break; + case Types.BIGINT: + this.data[i] = new long[numberOfRows]; + break; + case Types.REAL: + this.data[i] = new float[numberOfRows]; + break; + case Types.DOUBLE: + this.data[i] = new double[numberOfRows]; + break; + default: + this.data[i] = new Object[numberOfRows]; + } + } + } + // add to the backing array - Object[] next = this.data[++this.pos]; - this.protocol.parseTupleLine(line, next, this.jdbcSQLTypes); + int nextPos = ++this.pos; + this.protocol.parseTupleLine(nextPos, line, this.jdbcSQLTypes, this.data, this.nullMappings[nextPos]); } /** @@ -76,7 +111,7 @@ public class DataBlockResponse implement @Override public boolean wantsMore() { // remember: pos is the value already stored - return (this.pos + 1) < this.data.length; + return (this.pos + 1) < this.nullMappings.length; } /** @@ -87,8 +122,8 @@ public class DataBlockResponse implement */ @Override public void complete() throws SQLException { - if ((this.pos + 1) != this.data.length) { - throw new SQLException("Inconsistent state detected! Current block capacity: " + this.data.length + + if ((this.pos + 1) != this.nullMappings.length) { + throw new SQLException("Inconsistent state detected! Current block capacity: " + this.nullMappings.length + ", block usage: " + (this.pos + 1) + ". Did MonetDB send what it promised to?", "M0M10"); } } @@ -99,28 +134,109 @@ public class DataBlockResponse implement @Override public void close() { // feed all rows to the garbage collector - for (int i = 0; i < data.length; i++) { - for (int j = 0; j < data[0].length; j++) { - data[i][j] = null; - } + int numberOfColumns = this.data.length; + for (int i = 0; i < numberOfColumns; i++) { data[i] = null; + nullMappings[i] = null; + } + data = null; + nullMappings = null; + } + + /* Methods to be called after the block construction has been completed */ + + void setBlockLine(int blockLine) { + this.blockLine = blockLine; + } + + public void setData(Object[] data) { /* For VirtualResultSet :( */ + this.data = data; + } + + public Object[] getData() { /* For VirtualResultSet :( */ + return data; + } + + public boolean checkValueIsNull(int column) { + return this.nullMappings[this.blockLine][column]; + } + + public boolean getBooleanValue(int column) { + return ((boolean[]) this.data[column])[this.blockLine]; + } + + public byte getByteValue(int column) { + return ((byte[]) this.data[column])[this.blockLine]; + } + + public short getShortValue(int column) { + return ((short[]) this.data[column])[this.blockLine]; + } + + public int getIntValue(int column) { + return ((int[]) this.data[column])[this.blockLine]; + } + + public long getLongValue(int column) { + return ((long[]) this.data[column])[this.blockLine]; + } + + public float getFloatValue(int column) { + return ((float[]) this.data[column])[this.blockLine]; + } + + public double getDoubleValue(int column) { + return ((double[]) this.data[column])[this.blockLine]; + } + + public Object getObjectValue(int column) { + return ((Object[]) this.data[column])[this.blockLine]; + } + + public String getValueAsString(int column) { + switch (this.jdbcSQLTypes[column]) { + case Types.BOOLEAN: + return Boolean.toString(((boolean[]) this.data[column])[this.blockLine]); + case Types.TINYINT: + return Byte.toString(((byte[]) this.data[column])[this.blockLine]); + case Types.SMALLINT: + return Short.toString(((short[]) this.data[column])[this.blockLine]); + case Types.INTEGER: + return Integer.toString(((int[]) this.data[column])[this.blockLine]); + case Types.BIGINT: + return Long.toString(((long[]) this.data[column])[this.blockLine]); + case Types.REAL: + return Float.toString(((float[]) this.data[column])[this.blockLine]); + case Types.DOUBLE: + return Double.toString(((double[]) this.data[column])[this.blockLine]); + case Types.CHAR: + case Types.VARCHAR: + case Types.CLOB: + case Types.OTHER: + return (String) ((Object[]) this.data[column])[this.blockLine]; + default: + return ((Object[]) this.data[column])[this.blockLine].toString(); } } - /** - * Retrieves the required row. Warning: if the requested rows is out of bounds, an IndexOutOfBoundsException will - * be thrown. - * - * @param line the row to retrieve - * @return the requested row as String - */ - Object[] getRow(int line) { - if (forwardOnly) { - Object[] ret = data[line]; - data[line] = null; - return ret; - } else { - return data[line]; + public Object getValueAsObject(int column) { + switch (this.jdbcSQLTypes[column]) { + case Types.BOOLEAN: + return ((boolean[]) this.data[column])[this.blockLine]; + case Types.TINYINT: + return (((byte[]) this.data[column])[this.blockLine]); + case Types.SMALLINT: + return (((short[]) this.data[column])[this.blockLine]); + case Types.INTEGER: + return (((int[]) this.data[column])[this.blockLine]); + case Types.BIGINT: + return (((long[]) this.data[column])[this.blockLine]); + case Types.REAL: + return (((float[]) this.data[column])[this.blockLine]); + case Types.DOUBLE: + return (((double[]) this.data[column])[this.blockLine]); + default: + return ((Object[]) this.data[column])[this.blockLine]; } } } diff --git a/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java b/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java --- a/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java +++ b/src/main/java/nl/cwi/monetdb/mcl/responses/ResultSetResponse.java @@ -25,7 +25,7 @@ import java.sql.Types; */ public class ResultSetResponse implements IIncompleteResponse { - private static final byte IsSetFinalValue = 15; + private static final byte IS_SET_FINAL_VALUE = 15; /** The number of columns in this result */ private final int columncount; @@ -73,8 +73,7 @@ public class ResultSetResponse implement * @param tuplecount the total number of tuples in the result set * @param columncount the number of columns in the result set * @param rowcount the number of rows in the current block - * @param parent the parent that created this Response and will - * supply new result blocks when necessary + * @param parent the parent that created this Response and will supply new result blocks when necessary * @param seq the query sequence number */ public ResultSetResponse(MonetConnection con, MonetConnection.ResponseList parent, int id, int seq, int rowcount, @@ -139,7 +138,7 @@ public class ResultSetResponse implement */ @Override public boolean wantsMore() { - return this.isSet < IsSetFinalValue || resultBlocks[0].wantsMore(); + return this.isSet < IS_SET_FINAL_VALUE || resultBlocks[0].wantsMore(); } /** @@ -205,6 +204,11 @@ public class ResultSetResponse implement return type; } + /** + * Returns the JDBC types of the columns + * + * @return the JDBC types of the columns + */ public int[] getJdbcSQLTypes() { return JdbcSQLTypes; } @@ -272,7 +276,7 @@ public class ResultSetResponse implement */ @Override public void addLine(ServerResponses response, Object line) throws ProtocolException { - if (this.isSet >= IsSetFinalValue) { + if (this.isSet >= IS_SET_FINAL_VALUE) { this.resultBlocks[0].addLine(response, line); } else if (response != ServerResponses.HEADER) { throw new ProtocolException("header expected, got: " + response.toString()); @@ -307,7 +311,7 @@ public class ResultSetResponse implement * @return the exact row read as requested or null if the requested row is out of the scope of the result set * @throws SQLException if an database error occurs */ - public Object[] getLine(int row) throws SQLException { + public DataBlockResponse getDataBlockCorrespondingToLine(int row) throws SQLException { if (row >= tuplecount || row < 0) return null; @@ -355,7 +359,8 @@ public class ResultSetResponse implement throw new AssertionError("block " + block + " should have been fetched by now :("); } } - return rawr.getRow(blockLine); + rawr.setBlockLine(blockLine); + return rawr; } /**