Mercurial > hg > monetdb-java
diff src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java @ 0:a5a898f6886c
Copy of MonetDB java directory changeset e6e32756ad31.
author | Sjoerd Mullender <sjoerd@acm.org> |
---|---|
date | Wed, 21 Sep 2016 09:34:48 +0200 (2016-09-21) |
parents | |
children | a27ee2cb14a0 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java @@ -0,0 +1,3746 @@ +/* + * 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 - 2016 MonetDB B.V. + */ + +package nl.cwi.monetdb.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLData; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLInput; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +import nl.cwi.monetdb.mcl.parser.MCLParseException; +import nl.cwi.monetdb.mcl.parser.TupleLineParser; + +/** + * A ResultSet suitable for the MonetDB database. + * + * A table of data representing a database result set, which is usually + * generated by executing a statement that queries the database. + * + * A ResultSet object maintains a cursor pointing to its current row of data. + * Initially the cursor is positioned before the first row. The next method + * moves the cursor to the next row, and because it returns false when there + * are no more rows in the ResultSet object, it can be used in a while loop to + * iterate through the result set. + * + * The current state of this ResultSet is that it supports positioning in the + * result set, absolute and relative. A slight performance difference between + * FORWARD_ONLY or result sets scrollable in both directions can be noticed as + * for FORWARD_ONLY result sets the memory usage will be likely lower for large + * result sets. + * + * @author Fabian Groffen, Martin van Dinther + * @version 0.8 + */ +public class MonetResultSet extends MonetWrapper implements ResultSet { + // the following have default access modifier for the MonetVirtualResultSet subclass + /** The current line of the buffer split in columns */ + final TupleLineParser tlp; + /** The current position of the cursor for this ResultSet object */ + int curRow = 0; + + // a blank final is immutable once assigned in the constructor + /** A Header to retrieve lines from */ + private final MonetConnection.ResultSetResponse header; + /** The names of the columns in this ResultSet */ + private final String[] columns; + /** The MonetDB types of the columns in this ResultSet */ + private final String[] types; + /** The JDBC SQL types of the columns in this ResultSet. The content will be derived from the MonetDB types[] */ + private final int[] JdbcSQLTypes; + /** The number of rows in this ResultSet */ + final int tupleCount; // default for the MonetVirtualResultSet + + /** The parental Statement object */ + private final Statement statement; + + /** The type of this ResultSet (forward or scrollable) */ + private int type = TYPE_FORWARD_ONLY; + /** The concurrency of this ResultSet (currently only read-only) */ + private int concurrency = CONCUR_READ_ONLY; + /** The warnings for this ResultSet object */ + private SQLWarning warnings; + /** whether the last read field (via some getXyz() method) was NULL */ + private boolean lastReadWasNull = true; + /** Just a dummy variable to keep store the fetchsize set. */ + private int fetchSize; + + /** + * Main constructor backed by the given Header. + * + * @param statement the statement which created this ResultSet + * @param header a header containing the query, resultset type, etc. + * @throws SQLException is a protocol error occurs + */ + MonetResultSet( + Statement statement, + MonetConnection.ResultSetResponse header) + throws SQLException + { + if (statement == null) { + throw new IllegalArgumentException("Statement may not be null!"); + } + if (header == null) { + throw new IllegalArgumentException("ResultSetResponse may not be null!"); + } + this.statement = statement; + this.header = header; + this.type = header.getRSType(); + this.concurrency = header.getRSConcur(); + /* if we have a header object, the fetchSize used for this result set + is the header's cacheSize */ + this.fetchSize = header.getCacheSize(); + // well there is only one supported concurrency, so we don't have to + // bother about that + + // throws SQLException on getters of Header, so we find out immediately + // if an error occurred for this query + columns = header.getNames(); + types = header.getTypes(); + tupleCount = header.tuplecount; + + // create result array + tlp = new TupleLineParser(columns.length); + + JdbcSQLTypes = new int[types.length]; + populateJdbcSQLtypesArray(); + } + + /** + * Constructor used by MonetVirtualResultSet. + * DO NOT USE THIS CONSTRUCTOR IF YOU ARE NOT EXTENDING THIS OBJECT! + * + * @param statement the statement which created this ResultSet + * @param columns the column names + * @param types the column types + * @param results the number of rows in the ResultSet + * @throws IOException if communicating with monet failed + * @throws SQLException is a protocol error occurs + */ + MonetResultSet( + Statement statement, + String[] columns, + String[] types, + int results + ) throws IllegalArgumentException + { + if (statement == null) { + throw new IllegalArgumentException("Statement may not be null!"); + } + if (columns == null || types == null) { + throw new IllegalArgumentException("One of the given arguments is null!"); + } + if (columns.length != types.length) { + throw new IllegalArgumentException("Given arguments are not the same size!"); + } + if (results < 0) { + throw new IllegalArgumentException("Negative rowcount not allowed!"); + } + + this.statement = statement; + this.header = null; + this.fetchSize = 0; + + this.columns = columns; + this.types = types; + this.tupleCount = results; + + this.tlp = new TupleLineParser(columns.length); + + JdbcSQLTypes = new int[types.length]; + populateJdbcSQLtypesArray(); + } + + /** + * Internal utility method to fill the JdbcSQLTypes array with derivable values. + * By doing it once (in the constructor) we can avoid doing this in many getXyz() methods again and again + * thereby improving getXyz() method performance. + */ + private void populateJdbcSQLtypesArray() { + for (int i = 0; i < types.length; i++) { + int javaSQLtype = MonetDriver.getJavaType(types[i]); + JdbcSQLTypes[i] = javaSQLtype; + if (javaSQLtype == Types.BLOB) { + try { + if (((MonetConnection)statement.getConnection()).getBlobAsBinary()) + JdbcSQLTypes[i] = Types.BINARY; + } catch (SQLException se) { /* ignore it */ } + } + } + } + + + //== methods of interface ResultSet + + // Chapter 14.2.2 Sun JDBC 3.0 Specification + /** + * Moves the cursor to the given row number in this ResultSet object. + * + * If the row number is positive, the cursor moves to the given row number + * with respect to the beginning of the result set. The first row is row 1, + * the second is row 2, and so on. + * + * If the given row number is negative, the cursor moves to an absolute row + * position with respect to the end of the result set. For example, calling + * the method absolute(-1) positions the cursor on the last row; calling the + * method absolute(-2) moves the cursor to the next-to-last row, and so on. + * + * An attempt to position the cursor beyond the first/last row in the result + * set leaves the cursor before the first row or after the last row. + * Note: calling absolute(1) is the same as calling first(). Calling + * absolute(-1) is the same as calling last(). + * + * @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 access error occurs, or the result set + * type is TYPE_FORWARD_ONLY + */ + @Override + public boolean absolute(int row) throws SQLException { + if (row != curRow + 1 && type == TYPE_FORWARD_ONLY) throw + new SQLException("(Absolute) positioning not allowed on forward " + + " only result sets!", "M1M05"); + + if (header.isClosed()) + throw new SQLException("ResultSet is closed!", "M1M20"); + + // first calculate what the JDBC row is + if (row < 0) { + // calculate the negatives... + row = tupleCount + row + 1; + } + // now place the row not farther than just before or after the result + if (row < 0) row = 0; // before first + else if (row > tupleCount + 1) row = tupleCount + 1; // after last + + String tmpLine = header.getLine(row - 1); + + // store it + curRow = row; + + if (tmpLine == null) return false; + try { + tlp.parse(tmpLine); + } catch (MCLParseException e) { + throw new SQLException(e.getMessage(), "M0M10"); + } + + return true; + } + + /** + * 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 + */ + @Override + public void afterLast() throws SQLException { + absolute(tupleCount + 1); + } + + /** + * 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 + */ + @Override + public void beforeFirst() throws SQLException { + absolute(0); + } + + /** + * Clears all warnings reported for this ResultSet object. After a call to + * this method, the method getWarnings returns null until a new warning is + * reported for this ResultSet object. + */ + @Override + public void clearWarnings() { + warnings = null; + } + + /** + * Releases this ResultSet object's database (and JDBC) resources + * immediately instead of waiting for this to happen when it is + * automatically closed. + */ + @Override + public void close() { + if (header != null && !header.isClosed()) { + header.close(); + } + if (statement instanceof MonetStatement) + ((MonetStatement)statement).closeIfCompletion(); + } + + // Chapter 14.2.3 from Sun JDBC 3.0 specification + /** + * Maps the given ResultSet column name to its ResultSet column index. + * Column names supplied to getter methods are case insensitive. If a select + * list contains the same column more than once, the first instance of the + * column will be returned. + * + * @param columnName the name of the column + * @return the column index of the given column name + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public int findColumn(String columnName) throws SQLException { + if (columnName != null && columns != null) { + for (int i = 0; i < columns.length; i++) { + if (columnName.equals(columns[i])) + return i + 1; + } + /* if an exact match did not succeed try a case insensitive match */ + for (int i = 0; i < columns.length; i++) { + if (columnName.equalsIgnoreCase(columns[i])) + return i + 1; + } + } + throw new SQLException("No such column name: " + columnName, "M1M05"); + } + + /** + * Moves the cursor to the first row in this ResultSet object. + * + * @return true if the cursor is on a valid row; false if there are no rows + * in the result set + * @throws SQLException - if a database access error occurs or the result + * set type is TYPE_FORWARD_ONLY + */ + @Override + public boolean first() throws SQLException { + return absolute(1); + } + + @Override + public Array getArray(int columnIndex) throws SQLException { + throw newSQLFeatureNotSupportedException("getArray"); + } + @Override + public Array getArray(String colName) throws SQLException { + throw newSQLFeatureNotSupportedException("getArray"); + } + + /* Mapi doesn't allow something for streams at the moment, thus all not implemented for now */ + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException { + throw newSQLFeatureNotSupportedException("getAsciiStream"); + } + @Override + public InputStream getAsciiStream(String columnName) throws SQLException { + throw newSQLFeatureNotSupportedException("getAsciiStream"); + } + + @Override + @Deprecated + public InputStream getUnicodeStream(int columnIndex) throws SQLException { + throw newSQLFeatureNotSupportedException("getUnicodeStream"); + } + @Override + @Deprecated + public InputStream getUnicodeStream(String columnName) throws SQLException { + throw newSQLFeatureNotSupportedException("getUnicodeStream"); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a stream of uninterpreted bytes. The + * value can then be read in chunks from the stream. This method is + * particularly suitable for retrieving large LONGVARBINARY values. + * <br/><br/> + * Note: All the data in the returned stream must be read prior to + * getting the value of any other column. The next call to a getter + * method implicitly closes the stream. Also, a stream may return 0 + * when the method InputStream.available is called whether there is + * data available or not. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return a Java input stream that delivers the database column + * value as a stream of uninterpreted bytes; if the value is SQL + * NULL, the value returned is null + * @throws SQLException if the columnIndex is not valid; if a + * database access error occurs or this method is called on a closed + * result set + */ + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException { + try { + switch (JdbcSQLTypes[columnIndex - 1]) { + case Types.BLOB: + Blob blob = getBlob(columnIndex); + if (blob == null) + return null; + return blob.getBinaryStream(); + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + byte[] bte = getBytes(columnIndex); + if (bte == null) + return null; + return new ByteArrayInputStream(bte); + } + throw new SQLException("Cannot operate on " + types[columnIndex - 1] + " type", "M1M05"); + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a stream of uninterpreted bytes. The + * value can then be read in chunks from the stream. This method is + * particularly suitable for retrieving large LONGVARBINARY values. + * <br/><br/> + * Note: All the data in the returned stream must be read prior to + * getting the value of any other column. The next call to a getter + * method implicitly closes the stream. Also, a stream may return 0 + * when the method available is called whether there is data + * available or not. + * + * @param columnName the label for the column specified with + * the SQL AS clause. If the SQL AS clause was not specified, then + * the label is the name of the column + * @return a Java input stream that delivers the database column + * value as a stream of uninterpreted bytes; if the value is SQL + * NULL, the result is null + * @throws SQLException if the columnLabel is not valid; if a + * database access error occurs or this method is called on a closed + * result set + */ + @Override + public InputStream getBinaryStream(String columnName) throws SQLException { + return getBinaryStream(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.io.Reader object. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return a java.io.Reader object that contains the column value; + * if the value is SQL NULL, the value returned is null in + * the Java programming language. + * @throws SQLException if a database access error occurs + */ + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + return new StringReader(val); + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.io.Reader object. + * + * @param columnName the name of the column + * @return a java.io.Reader object that contains the column value; + * if the value is SQL NULL, the value returned is null in + * the Java programming language. + * @throws SQLException if a database access error occurs + */ + @Override + public Reader getCharacterStream(String columnName) throws SQLException { + return getCharacterStream(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.io.Reader object. It is + * intended for use when accessing NCHAR,NVARCHAR and LONGNVARCHAR + * columns. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return a java.io.Reader object that contains the column value; + * if the value is SQL NULL, the value returned is null in + * the Java programming language. + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException { + throw newSQLFeatureNotSupportedException("getNCharacterStream"); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.io.Reader object. It is + * intended for use when accessing NCHAR,NVARCHAR and LONGNVARCHAR + * columns. + * + * @param columnName the name of the column + * @return a java.io.Reader object that contains the column value; + * if the value is SQL NULL, the value returned is null in + * the Java programming language. + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public Reader getNCharacterStream(String columnName) throws SQLException { + throw newSQLFeatureNotSupportedException("getNCharacterStream"); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a Blob object in the Java programming + * language. + * + * @param i the first column is 1, the second is 2, ... + * @return a Blob object representing the SQL BLOB value in the + * specified column + * @throws SQLException if a database access error occurs + */ + @Override + public Blob getBlob(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + return MonetBlob.create(val); + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a Blob object in the Java programming + * language. + * + * @param colName the name of the column from which to retrieve + * the value + * @return a Blob object representing the SQL BLOB value in the + * specified column + * @throws SQLException if a database access error occurs + */ + @Override + public Blob getBlob(String colName) throws SQLException { + return getBlob(findColumn(colName)); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a Clob object in the + * Java programming language. + * + * @param i the first column is 1, the second is 2, ... + * @return a Clob object representing the SQL CLOB value in the + * specified column + * @throws SQLException if a database access error occurs + */ + @Override + public Clob getClob(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + return new MonetClob(val); + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a Clob object in the + * Java programming language. + * + * @param colName the name of the column from which to retrieve + * the value + * @return a Clob object representing the SQL CLOB value in the + * specified column + * @throws SQLException if a database access error occurs + */ + @Override + public Clob getClob(String colName) throws SQLException { + return getClob(findColumn(colName)); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a NClob object in the + * Java programming language. + * + * @param i the first column is 1, the second is 2, ... + * @return a NClob object representing the SQL NCLOB value in the + * specified column + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public NClob getNClob(int i) throws SQLException { + throw newSQLFeatureNotSupportedException("getNClob"); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a NClob object in the + * Java programming language. + * + * @param colName the name of the column from which to retrieve + * the value + * @return a NClob object representing the SQL NCLOB value in the + * specified column + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public NClob getNClob(String colName) throws SQLException { + throw newSQLFeatureNotSupportedException("getNClob"); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.math.BigDecimal with full precision. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value (full precision); if the value is SQL NULL, + * the value returned is null in the Java programming language. + * @throws SQLException if a database access error occurs + */ + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + try { + return new BigDecimal(val); + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.math.BigDecimal with full precision. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @param scale the number of digits to the right of the decimal point + * @return the column value (full precision); if the value is SQL NULL, + * the value returned is null in the Java programming language. + * @throws SQLException if a database access error occurs + */ + @Override + @Deprecated + public BigDecimal getBigDecimal(int columnIndex, int scale) + throws SQLException + { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + try { + BigDecimal bd = new BigDecimal(val); + bd.setScale(scale); + return bd; + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.math.BigDecimal with full precision. + * + * @param columnName the SQL name of the column + * @return the column value (full precision); if the value is SQL NULL, + * the value returned is null in the Java programming language. + * @throws SQLException if a database access error occurs + */ + @Override + public BigDecimal getBigDecimal(String columnName) throws SQLException { + return getBigDecimal(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.math.BigDecimal with full precision. + * + * @param columnName the SQL name of the column + * @param scale the number of digits to the right of the decimal point + * @return the column value (full precision); if the value is SQL NULL, + * the value returned is null in the Java programming language. + * @throws SQLException if a database access error occurs + */ + @Override + @Deprecated + public BigDecimal getBigDecimal(String columnName, int scale) + throws SQLException + { + return getBigDecimal(findColumn(columnName), scale); + } + + // See Sun JDBC Specification 3.0 Table B-6 + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a boolean in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is false + * @throws SQLException if there is no such column + */ + @Override + public boolean getBoolean(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return false; // if the value is SQL NULL, the value returned is false + } + lastReadWasNull = false; + + // match common cases first + if ("false".equalsIgnoreCase(val) || "0".equals(val)) + return false; + if ("true".equalsIgnoreCase(val) || "1".equals(val)) + return true; + + // match type specific values + switch (JdbcSQLTypes[columnIndex - 1]) { + case Types.BOOLEAN: + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.CLOB: + // check if string value equals "true" (case insensitive) or not + return Boolean.parseBoolean(val); + case Types.BIT: // MonetDB doesn't use type BinaryDigit, it's here for completeness + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + if (getInt(columnIndex) == 0) { + return false; + } + return true; + case Types.BIGINT: + if (getLong(columnIndex) == 0L) { + return false; + } + return true; + case Types.DOUBLE: + case Types.FLOAT: + case Types.REAL: + if (getDouble(columnIndex) == 0.0) { + return false; + } + return true; + case Types.DECIMAL: + case Types.NUMERIC: + if (getBigDecimal(columnIndex).compareTo(BigDecimal.ZERO) == 0) { + return false; + } + return true; + default: + throw new SQLException("Conversion from " + types[columnIndex - 1] + " to boolean type not supported", "M1M05"); + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a boolean in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is false + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public boolean getBoolean(String columnName) throws SQLException { + return getBoolean(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a byte in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if a database access error occurs + */ + @Override + public byte getByte(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return (byte) 0; + } + lastReadWasNull = false; + try { + return Byte.parseByte(val); + } catch (NumberFormatException e) { + // ignore parse error, return the default: 0 + return (byte) 0; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a byte in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if a database access error occurs + */ + @Override + public byte getByte(String columnName) throws SQLException { + return getByte(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a byte array in the Java programming language. The + * bytes represent the raw values returned by the driver. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public byte[] getBytes(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + + // According to Table B-6, getBytes() only operates on BINARY types + switch (JdbcSQLTypes[columnIndex - 1]) { + case Types.BLOB: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + // unpack the HEX (BLOB) notation to real bytes + int len = val.length() / 2; + byte[] buf = new byte[len]; + int offset; + for (int j = 0; j < len; j++) { + offset = j * 2; + buf[j] = (byte)Integer.parseInt(val.substring(offset, offset + 2), 16); + } + return buf; + default: + throw new SQLException("Cannot operate on " + types[columnIndex - 1] + " type", "M1M05"); + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a byte array in the Java programming language. The + * bytes represent the raw values returned by the driver. + * + * NOTE: Since the mapi protocol is ASCII-based, this method only returns + * Java byte representations of Strings, which is nothing more than + * an encoding into a sequence of bytes using the platform's default + * charset. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public byte[] getBytes(String columnName) throws SQLException { + return getBytes(findColumn(columnName)); + } + + /** + * Retrieves the concurrency mode of this ResultSet object. The concurrency + * used is determined by the Statement object that created the result set. + * + * NOTE: MonetDB only supports read-only result sets, and will always return + * ResultSet.CONCUR_READ_ONLY + * + * @return the concurrency type, either ResultSet.CONCUR_READ_ONLY or + * ResultSet.CONCUR_UPDATABLE + */ + @Override + public int getConcurrency() { + return concurrency; + } + + /** + * Retrieves the name of the SQL cursor used by this ResultSet object. + * In SQL, a result table is retrieved through a cursor that is named. + * For MonetDB this is the header.id returned in a resultset header. The + * current row of a result set can be updated or deleted using a positioned + * update/delete statement that references the cursor name. To insure that + * the cursor has the proper isolation level to support update, the + * cursor's SELECT statement should be of the form SELECT FOR UPDATE. If + * FOR UPDATE is omitted, the positioned updates may fail. + * + * The JDBC API supports this SQL feature by providing the name of the SQL + * cursor used by a ResultSet object. The current row of a ResultSet object + * is also the current row of this SQL cursor. + * + * Note: If positioned update is not supported, a SQLException is thrown. + * MonetDB currently doesn't support updates, so the SQLException is + * thrown for now. + * + * @return the SQL name for this ResultSet object's cursor + * @throws SQLException if a database access error occurs + */ + @Override + public String getCursorName() throws SQLException { + throw new SQLException("Positioned updates not supported for this " + + "cursor (" + (header != null ? header.id : "") + ")", "0AM21"); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a double in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if there is no such column + */ + @Override + public double getDouble(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return 0; + } + lastReadWasNull = false; + try { + return Double.parseDouble(val); + } catch (NumberFormatException e) { + // ignore conversion error, return the default: 0 + return 0; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a double in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public double getDouble(String columnName) throws SQLException { + return getDouble(findColumn(columnName)); + } + + /** + * Retrieves the holdability of this ResultSet object. + * + * @return either ResultSet.HOLD_CURSORS_OVER_COMMIT or + * ResultSet.CLOSE_CURSORS_AT_COMMIT + * @throws SQLException if a database access error occurs + */ + @Override + public int getHoldability() throws SQLException { + return getStatement().getConnection().getHoldability(); + } + + /** + * Retrieves the fetch direction for this ResultSet object. + * <b>currently not implemented</b> + * + * @return the current fetch direction for this ResultSet object + */ + @Override + public int getFetchDirection() { + return ResultSet.FETCH_FORWARD; + } + + /** + * Gives a hint as to the direction in which the rows in this ResultSet + * object will be processed. The initial value is determined by the + * Statement object that produced this ResultSet object. + * The fetch direction may be changed at any time. + * <b>currently not implemented</b> + * + * @param direction - an int specifying the suggested fetch direction; + * one of ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, or ResultSet.FETCH_UNKNOWN + */ + @Override + public void setFetchDirection(int direction) throws SQLException { + switch (direction) { + case ResultSet.FETCH_FORWARD: + break; + case ResultSet.FETCH_REVERSE: + case ResultSet.FETCH_UNKNOWN: + throw new SQLException("Not supported direction " + direction, "0A000"); + default: + throw new SQLException("Illegal direction: " + direction, "M1M05"); + } + } + + /** + * Retrieves the fetch size for this ResultSet object. + * + * @return the current fetch size for this ResultSet object + * @throws SQLException if a database access error occurs + */ + @Override + public int getFetchSize() throws SQLException { + return fetchSize; + } + + /** + * Gives the JDBC driver a hint as to the number of rows that should be + * fetched from the database when more rows are needed. In MonetDB, this is + * actually a no-op, because even before a MonetResultSet object is + * created, the fetch size is already determined in the + * MonetConnection.ResultSetResponse passed to its constructor. Since all + * data blocks for this whole result set are already allocated in + * MonetConnection.ResultSetResponse, it is too complicated and error-prone + * to still change the fetchSize here. If one really needs to overwrite + * the default fetchSize, please use MonetStatement.setFetchSize() instead. + * + * @param rows the number of rows to fetch + * @throws SQLException if the condition 0 <= rows is not satisfied + */ + @Override + public void setFetchSize(int rows) throws SQLException { + if (rows >= 0) { + fetchSize = rows; + } else { + throw new SQLException("Illegal fetch size value: " + rows, "M1M05"); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a float in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if there is no such column + */ + @Override + public float getFloat(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return 0; + } + lastReadWasNull = false; + try { + return Float.parseFloat(val); + } catch (NumberFormatException e) { + // ignore conversion error, return the default: 0 + return 0; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a float in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public float getFloat(String columnName) throws SQLException { + return getFloat(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as an int in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if there is no such column + */ + @Override + public int getInt(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return 0; + } + lastReadWasNull = false; + try { + return Integer.parseInt(val); + } catch (NumberFormatException e) { + // ignore conversion error, return the default: 0 + return 0; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as an int in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public int getInt(String columnName) throws SQLException { + return getInt(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a long in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if there is no such column + */ + @Override + public long getLong(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return 0; + } + lastReadWasNull = false; + + // The oid datatype values (as string) have a @0 suffix in the string value. + // To allow succesful parsing and conversion to long, we need to remove it first + if ("oid".equals(types[columnIndex - 1])) { + int len = val.length(); + if (len > 2 && val.endsWith("@0")) + val = val.substring(0, len-2); + } + try { + return Long.parseLong(val); + } catch (NumberFormatException e) { + // ignore conversion error, return the default: 0 + return 0; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a long in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public long getLong(String columnName) throws SQLException { + return getLong(findColumn(columnName)); + } + + /* helper for the anonymous class inside getMetaData */ + private abstract class rsmdw extends MonetWrapper implements ResultSetMetaData {} + /** + * Retrieves the number, types and properties of this ResultSet object's + * columns. + * + * @return the description of this ResultSet object's columns + */ + @Override + public ResultSetMetaData getMetaData() { + // return inner class which implements the ResultSetMetaData interface + return new rsmdw() { + // for the more expensive methods (getPrecision(), getScale(), isNullable()), we provide a simple cache + // caches to store precision, scale and isNullable values from getColumns() + private boolean[] _is_fetched = new boolean[columns.length +1]; + private int[] _precision = new int[columns.length +1]; + private int[] _scale = new int[columns.length +1]; + private int[] _isNullable = new int[columns.length +1]; + private boolean[] _isAutoincrement = new boolean[columns.length +1]; + private Connection conn = null; + private DatabaseMetaData dbmd = null; + + /** + * A private method to fetch the precision, scale, isNullable and isAutoincrement value for a fully qualified column. + * As md.getColumns() is an expensive method we call it only once per column + * and cache the precision, scale, isNullable and isAutoincrement values in the above array chaches. + * Also we only call md.getColumns() when we have a non empty schema name and table name and column name. + */ + private void fetchColumnInfo(int column) throws SQLException + { + if (column <= 0 || column > columns.length) + throw newSQLInvalidColumnIndexException(column); + + _is_fetched[column] = true; + _precision[column] = 0; + _scale[column] = 0; + _isNullable[column] = columnNullableUnknown; + _isAutoincrement[column] = false; + + // we can only call dbmd.getColumns() when we have a specific schema name and table name and column name + String schName = getSchemaName(column); + if (schName != null && !"".equals(schName)) { + String tblName = getTableName(column); + if (tblName != null && !"".equals(tblName)) { + String colName = getColumnName(column); + if (colName != null && !"".equals(colName)) { + if (conn == null) { + // first time, get a Connection object and cache it for all next columns + conn = getStatement().getConnection(); + } + if (conn != null && dbmd == null) { + // first time, get a MetaData object and cache it for all next columns + dbmd = conn.getMetaData(); + } + if (dbmd != null) { + // for precision, scale, isNullable and isAutoincrement we query the information from data dictionary + ResultSet colInfo = dbmd.getColumns(null, schName, tblName, colName); + if (colInfo != null) { + // we expect exactly one row in the resultset + if (colInfo.next()) { + _precision[column] = colInfo.getInt(7); // col 7 is "COLUMN_SIZE" + _scale[column] = colInfo.getInt(9); // col 9 is "DECIMAL_DIGITS" + _isNullable[column] = colInfo.getInt(11); // col 11 is "NULLABLE" + String strVal = colInfo.getString(23); // col 23 is "IS_AUTOINCREMENT" + if (strVal != null && "YES".equals(strVal)) + _isAutoincrement[column] = true; + } + colInfo.close(); // close the resultset to release resources + } + } + } + } + } + } + + /** + * Returns the number of columns in this ResultSet object. + * + * @returns the number of columns + */ + @Override + public int getColumnCount() { + return columns.length; + } + + /** + * Indicates whether the designated column is automatically numbered. + * + * @param column the first column is 1, the second is 2, ... + * @return true if so; false otherwise + * @throws SQLException if a database access error occurs + */ + @Override + public boolean isAutoIncrement(int column) throws SQLException { + if (_is_fetched[column] != true) { + fetchColumnInfo(column); + } + return _isAutoincrement[column]; + } + + /** + * Indicates whether a column's case matters. + * + * @param column the first column is 1, the second is 2, ... + * @returns true for all character string columns else false + */ + @Override + public boolean isCaseSensitive(int column) throws SQLException { + switch (getColumnType(column)) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.CLOB: + return true; + } + + return false; + } + + /** + * Indicates whether the designated column can be used in a + * where clause. + * It is unknown to me what kind ot columns they regard to, + * as I think all columns are useable in a where clause. + * Returning true for all here, for the time being. + * Possible thought; maybe they want to know here if it's a + * real column existing in a table or not... + * + * @param column the first column is 1, the second is 2, ... + * @returns true + */ + @Override + public boolean isSearchable(int column) { + return true; + } + + /** + * 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, ... + * @returns false + */ + @Override + public boolean isCurrency(int column) { + return false; + } + + /** + * 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, ... + * @return true if so; false otherwise + */ + @Override + public boolean isSigned(int column) throws SQLException { + String monettype = getColumnTypeName(column); + if (monettype != null) { + if ("oid".equals(monettype) + || "ptr".equals(monettype)) + return false; + } + // we can hardcode this, based on the colum type + switch (getColumnType(column)) { + case Types.NUMERIC: + case Types.DECIMAL: + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + return true; + case Types.BIT: // we don't use type BIT, it's here for completeness + case Types.BOOLEAN: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + default: + return false; + } + } + + /** + * 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 + * @throws SQLException if there is no such column + */ + @Override + public int getColumnDisplaySize(int column) throws SQLException { + int ret = 1; + + if (header == null) + return ret; + + try { + ret = header.getColumnLengths()[column - 1]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(column); + } + + return ret; + } + + /** + * Get the designated column's schema name. + * + * @param column the first column is 1, the second is 2, ... + * @return schema name or "" if not applicable + * @throws SQLException if a database access error occurs + */ + @Override + public String getSchemaName(int column) throws SQLException { + String schema = ""; + + if (header == null) + return schema; + + // figure the name out + try { + schema = header.getTableNames()[column - 1]; + if (schema != null) { + int dot = schema.indexOf("."); + return (dot >= 0) ? schema.substring(0, dot) : ""; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(column); + } + + return ""; + } + + /** + * Gets the designated column's table name. + * + * @param column the first column is 1, the second is 2, ... + * @return table name or "" if not applicable + */ + @Override + public String getTableName(int column) throws SQLException { + String table = ""; + + if (header == null) + return table; + + // figure the name out + try { + table = header.getTableNames()[column - 1]; + if (table != null) { + int dot = table.indexOf("."); + return (dot >= 0) ? table.substring(dot + 1) : table; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(column); + } + + return ""; + } + + /** + * 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 + * @throws SQLException if a database access error occurs + */ + @Override + public int getPrecision(int column) throws SQLException { + if (_is_fetched[column] != true) { + fetchColumnInfo(column); + } + if (_precision[column] == 0) { + // apparently no precision could be fetched + // use columnDisplaySize() value for variable length data types + switch (getColumnType(column)) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.CLOB: + case Types.BLOB: + case Types.NUMERIC: + case Types.DECIMAL: + _precision[column] = getColumnDisplaySize(column); + break; + case Types.TINYINT: + _precision[column] = 3; + break; + case Types.SMALLINT: + _precision[column] = 5; + break; + case Types.INTEGER: + _precision[column] = 10; + break; + case Types.BIGINT: + _precision[column] = 19; + break; + case Types.REAL: + _precision[column] = 7; + break; + case Types.FLOAT: + case Types.DOUBLE: + _precision[column] = 15; + break; + case Types.BIT: // MonetDB doesn't use type BIT, it's here for completeness + _precision[column] = 1; + break; + case Types.BOOLEAN: + _precision[column] = 5; + break; + case Types.DATE: + _precision[column] = 10; + break; + case Types.TIME: + _precision[column] = 8; + break; + case Types.TIMESTAMP: + _precision[column] = 19; + break; + default: + _precision[column] = 30; + break; + } + } + return _precision[column]; + } + + /** + * 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 + * @throws SQLException if a database access error occurs + */ + @Override + public int getScale(int column) throws SQLException { + if (_is_fetched[column] != true) { + fetchColumnInfo(column); + } + return _scale[column]; + } + + /** + * 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 the nullability status of the given column; one of columnNoNulls, columnNullable or columnNullableUnknown + * @throws SQLException if a database access error occurs + */ + @Override + public int isNullable(int column) throws SQLException { + if (_is_fetched[column] != true) { + fetchColumnInfo(column); + } + return _isNullable[column]; + } + + /** + * Gets the designated column's table's catalog name. + * 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 + */ + @Override + public String getCatalogName(int column) throws SQLException { + return null; // MonetDB does NOT support catalogs + + } + + /** + * 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 + */ + @Override + public boolean isReadOnly(int column) { + return true; + } + + /** + * 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 + */ + @Override + public boolean isWritable(int column) { + return false; + } + + /** + * 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 + */ + @Override + public boolean isDefinitelyWritable(int column) { + return false; + } + + /** + * 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. + * @throws SQLException if there is no such column + */ + @Override + public String getColumnClassName(int column) throws SQLException { + if (conn == null) { + // first time, get a Connection object and cache it for all next columns + conn = getStatement().getConnection(); + } + try { + Class<?> type = null; + if (conn != null) { + Map<String,Class<?>> map = conn.getTypeMap(); + if (map != null && map.containsKey(types[column - 1])) { + type = (Class)map.get(types[column - 1]); + } + } + if (type == null) { + // fallback to the standard SQL type Class mappings + type = getClassForType(JdbcSQLTypes[column - 1]); + } + if (type != null) { + return type.getName(); + } + throw new SQLException("column type mapping null: " + types[column - 1], "M0M03"); + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(column); + } + } + + /** + * 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 + * @throws SQLException if there is no such column + */ + @Override + public String getColumnLabel(int column) throws SQLException { + return getColumnName(column); + } + + /** + * Gets the designated column's name + * + * @param column the first column is 1, the second is 2, ... + * @return the column name + * @throws SQLException if there is no such column + */ + @Override + public String getColumnName(int column) throws SQLException { + try { + return columns[column - 1]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(column); + } + } + + /** + * Retrieves the designated column's SQL type. + * + * @param column the first column is 1, the second is 2, ... + * @return SQL type from java.sql.Types + * @throws SQLException if there is no such column + */ + @Override + public int getColumnType(int column) throws SQLException { + try { + return JdbcSQLTypes[column - 1]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(column); + } + } + + /** + * 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. + * @throws SQLException if there is no such column + */ + @Override + public String getColumnTypeName(int column) throws SQLException { + try { + return types[column - 1]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(column); + } + } + + }; // end of new rsmdw() + } // end of getMetaData() + + /** + * Gets the value of the designated column in the current row of this + * ResultSet object as an Object in the Java programming language. + * + * This method will return the value of the given column as a Java object. + * The type of the Java object will be the default Java object type + * corresponding to the column's SQL type, following the mapping for + * built-in types specified in the JDBC specification. If the value is + * an SQL NULL, the driver returns a Java null. + * + * This method may also be used to read database-specific abstract data + * types. In the JDBC 2.0 API, the behavior of method getObject is extended + * to materialize data of SQL user-defined types. When a column contains a + * structured or distinct value, the behavior of this method is as if it + * were a call to: getObject(columnIndex, this.getStatement().getConnection().getTypeMap()). + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return a java.lang.Object holding the column value or null + * @throws SQLException if a database access error occurs + */ + @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 + final String MonetDBType; + final int JdbcType; + final String val; + try { + val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + MonetDBType = types[columnIndex - 1]; + JdbcType = JdbcSQLTypes[columnIndex - 1]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + + switch(JdbcType) { + case Types.BIT: // MonetDB doesn't use type BInary digiT, it's here for completeness + case Types.TINYINT: + case Types.SMALLINT: + try { + return Short.valueOf(val); + } catch (NumberFormatException e) { + return val; + } + case Types.INTEGER: + try { + return Integer.valueOf(val); + } catch (NumberFormatException e) { + return val; + } + case Types.BIGINT: + try { + return Long.valueOf(val); + } catch (NumberFormatException e) { + return val; + } + case Types.DOUBLE: + case Types.FLOAT: + try { + return Double.valueOf(val); + } catch (NumberFormatException e) { + return val; + } + case Types.REAL: + try { + return Float.valueOf(val); + } catch (NumberFormatException e) { + return val; + } + case Types.DECIMAL: + case Types.NUMERIC: + try { + return new BigDecimal(val); + } catch (NumberFormatException e) { + return val; + } + case Types.BOOLEAN: + return Boolean.valueOf(val); + case Types.VARCHAR: + { + // 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. + // As in most cases when we get here the MonetDBtype will be "varchar". For efficiency we compare + // the MonetDBtype first letter to see if we can skip the three .equals(MonetDBType) method calls. + char firstletter = MonetDBType.charAt(0); + if (firstletter == 'i') { + if ("inet".equals(MonetDBType)) { + try { + nl.cwi.monetdb.jdbc.types.INET inet_obj = new nl.cwi.monetdb.jdbc.types.INET(); + inet_obj.fromString(val); + return inet_obj; + } catch (Exception exc) { + // ignore exception and just return the val String object + return val; + } + } + } + else if (firstletter == 'u') { + if ("url".equals(MonetDBType)) { + try { + nl.cwi.monetdb.jdbc.types.URL url_obj = new nl.cwi.monetdb.jdbc.types.URL(); + url_obj.fromString(val); + return url_obj; + } catch (MalformedURLException exc) { + // ignore exception and just return the val String object + return val; + } catch (Exception exc) { + // ignore exception and just return the val String object + return val; + } + } + if ("uuid".equals(MonetDBType)) { + try { + return java.util.UUID.fromString(val); + } catch (IllegalArgumentException exc) { + // ignore exception and just return the val String object + return val; + } + } + } +// else if (firstletter == 'j') { +// 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/) + // see also https://github.com/fabienrenaud/java-json-benchmark + // Note that it would make our JDBC driver dependent of an external jar + // and we don't want that so simply return it as String object +// return val; +// } +// } + return val; + } + case Types.CHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + return val; + case Types.CLOB: + return new MonetClob(val); + case Types.BLOB: + return MonetBlob.create(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. + return getObject(columnIndex, this.getStatement().getConnection().getTypeMap()); + } + } + + private boolean classImplementsSQLData(Class<?> cl) { + Class<?>[] cls = cl.getInterfaces(); + for (int i = 0; i < cls.length; i++) { + if (cls[i] == SQLData.class) + return true; + } + return false; + } + + /** + * Gets the value of the designated column in the current row of this + * ResultSet object as an Object in the Java programming language. + * + * This method will return the value of the given column as a Java object. + * The type of the Java object will be the default Java object type corresponding + * to the column's SQL type, following the mapping for built-in types specified + * in the JDBC specification. + * If the value is an SQL NULL, the driver returns a Java null. + * + * This method may also be used to read database-specific abstract data types. + * In the JDBC 2.0 API, the behavior of method getObject is extended to + * materialize data of SQL user-defined types. + * + * If Connection.getTypeMap does not throw a SQLFeatureNotSupportedException, then + * when a column contains a structured or distinct value, the behavior of this + * method is as if it were a call to: getObject(columnIndex, + * this.getStatement().getConnection().getTypeMap()). + * If Connection.getTypeMap does throw a SQLFeatureNotSupportedException, then + * structured values are not supported, and distinct values are mapped to the + * default Java class as determined by the underlying SQL type of the DISTINCT type. + * + * @param i the first column is 1, the second is 2, ... + * @param map a java.util.Map object that contains the mapping from SQL + * type names to classes in the Java programming language + * @return an Object in the Java programming language representing the SQL + * value + * @throws SQLException if a database access error occurs + */ + @Override + @SuppressWarnings("unchecked") + public Object getObject(int columnIndex, Map<String,Class<?>> map) + throws SQLException + { + final String val; + final String MonetDBtype; + try { + val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + MonetDBtype = types[columnIndex - 1]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + + Class<?> type = null; + if (map != null && map.containsKey(MonetDBtype)) { + type = map.get(MonetDBtype); + } + if (type == null) { + // fallback to the standard Class mappings + type = getClassForType(JdbcSQLTypes[columnIndex - 1]); + } + + if (type == null || type == String.class) { + return val; + } else if (type == BigDecimal.class) { + return getBigDecimal(columnIndex); + } else if (type == Boolean.class) { + return Boolean.valueOf(getBoolean(columnIndex)); + } else if (type == Short.class) { + return Short.valueOf(getShort(columnIndex)); + } else if (type == Integer.class) { + return Integer.valueOf(getInt(columnIndex)); + } else if (type == Long.class) { + return Long.valueOf(getLong(columnIndex)); + } else if (type == Float.class) { + return Float.valueOf(getFloat(columnIndex)); + } else if (type == Double.class) { + return Double.valueOf(getDouble(columnIndex)); + } else if (type == byte[].class) { + return getBytes(columnIndex); + } else if (type == java.sql.Date.class) { + return getDate(columnIndex); + } else if (type == Time.class) { + return getTime(columnIndex); + } else if (type == Timestamp.class) { + return getTimestamp(columnIndex); + } else if (type == Clob.class) { + return getClob(columnIndex); + } else if (type == Blob.class) { + return getBlob(columnIndex); + } else if (classImplementsSQLData(type)) { + SQLData x; + try { + Constructor<? extends SQLData> ctor = + ((Class)type).getConstructor(); + x = ctor.newInstance(); + } catch (NoSuchMethodException nsme) { + throw new SQLException(nsme.getMessage(), "M0M27"); + } catch (InstantiationException ie) { + throw new SQLException(ie.getMessage(), "M0M27"); + } catch (IllegalAccessException iae) { + throw new SQLException(iae.getMessage(), "M0M27"); + } catch (InvocationTargetException ite) { + throw new SQLException(ite.getMessage(), "M0M27"); + } catch (SecurityException se) { + throw new SQLException(se.getMessage(), "M0M27"); + } + final int colnum = columnIndex; + final boolean valwasnull = wasNull(); + SQLInput input = new SQLInput() { + @Override + public String readString() throws SQLException { + return getString(colnum); + } + + @Override + public boolean readBoolean() throws SQLException { + return getBoolean(colnum); + } + + @Override + public byte readByte() throws SQLException { + return getByte(colnum); + } + + @Override + public short readShort() throws SQLException { + return getShort(colnum); + } + + @Override + public int readInt() throws SQLException { + return getInt(colnum); + } + + @Override + public long readLong() throws SQLException { + return getLong(colnum); + } + + @Override + public float readFloat() throws SQLException { + return getFloat(colnum); + } + + @Override + public double readDouble() throws SQLException { + return getDouble(colnum); + } + + @Override + public BigDecimal readBigDecimal() throws SQLException { + return getBigDecimal(colnum); + } + + @Override + public byte[] readBytes() throws SQLException { + return getBytes(colnum); + } + + @Override + public java.sql.Date readDate() throws SQLException { + return getDate(colnum); + } + + @Override + public java.sql.Time readTime() throws SQLException { + return getTime(colnum); + } + + @Override + public Timestamp readTimestamp() throws SQLException { + return getTimestamp(colnum); + } + + @Override + public Reader readCharacterStream() throws SQLException { + return getCharacterStream(colnum); + } + + @Override + public InputStream readAsciiStream() throws SQLException { + return getAsciiStream(colnum); + } + + @Override + public InputStream readBinaryStream() throws SQLException { + return getBinaryStream(colnum); + } + + @Override + public Object readObject() throws SQLException { + return getObject(colnum); + } + + @Override + public Ref readRef() throws SQLException { + return getRef(colnum); + } + + @Override + public Blob readBlob() throws SQLException { + return getBlob(colnum); + } + + @Override + public Clob readClob() throws SQLException { + return getClob(colnum); + } + + @Override + public Array readArray() throws SQLException { + return getArray(colnum); + } + + @Override + public boolean wasNull() throws SQLException { + return valwasnull; + } + + @Override + public URL readURL() throws SQLException { + return getURL(colnum); + } + + @Override + public NClob readNClob() throws SQLException { + return getNClob(colnum); + } + + @Override + public String readNString() throws SQLException { + return getNString(colnum); + } + + @Override + public SQLXML readSQLXML() throws SQLException { + return getSQLXML(colnum); + } + + @Override + public RowId readRowId() throws SQLException { + return getRowId(colnum); + } + }; + x.readSQL(input, MonetDBtype); + return x; + } else { + return val; + } + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object and will convert from the SQL type of + * the column to the requested Java data type, if the conversion is + * supported. If the conversion is not supported or null is + * specified for the type, a SQLException is thrown. + * + * At a minimum, an implementation must support the conversions defined + * in Appendix B, Table B-3 and conversion of appropriate user defined + * SQL types to a Java type which implements SQLData, or Struct. + * Additional conversions may be supported and are vendor defined. + * + * @param i the first column is 1, the second is 2, ... + * @param type Class representing the Java data type to convert the + * designated column to + * @return an instance of type holding the column value + * @throws SQLException if conversion is not supported, type is + * null or another error occurs. The getCause() method of + * the exception may provide a more detailed exception, for + * example, if a conversion error occurs + */ + @Override + public <T> T getObject(int i, Class<T> type) throws SQLException { + if (type == null) + throw new SQLException("type is null", "M1M05"); + + throw new SQLFeatureNotSupportedException("cannot return a Java generic type based on static types from getXXX methods", "0AM34"); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object and will convert from the SQL type of + * the column to the requested Java data type, if the conversion is + * supported. If the conversion is not supported or null is + * specified for the type, a SQLException is thrown. + * + * @param columnLabel the label for the column specified with the + * SQL AS clause. If the SQL AS clause was not specified, + * then the label is the name of the column + * @param type Class representing the Java data type to convert the + * designated column to + * @return an instance of type holding the column value + * @throws SQLException if conversion is not supported, type is + * null or another error occurs. The getCause() method of + * the exception may provide a more detailed exception, for + * example, if a conversion error occurs + */ + @Override + public <T> T getObject(String columnLabel, Class<T> type) + throws SQLException + { + return getObject(findColumn(columnLabel), type); + } + + /** + * Helper method to support the getObject and + * ResultsetMetaData.getColumnClassName JDBC methods. + * + * @param type a value from java.sql.Types + * @return a Class object from which an instance would be returned + */ + static Class<?> getClassForType(int type) { + /** + * This switch returns the types as objects according to table B-3 from + * Oracle's JDBC specification 4.1 + */ + // keep this switch regarding the returned classes aligned with getObject(int, Map) ! + switch(type) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + return String.class; + case Types.NUMERIC: + case Types.DECIMAL: + return BigDecimal.class; + case Types.BOOLEAN: + return Boolean.class; + case Types.BIT: // MonetDB doesn't support type BIT, it's here for completeness + case Types.TINYINT: + case Types.SMALLINT: + return Short.class; + case Types.INTEGER: + return Integer.class; + case Types.BIGINT: + return Long.class; + case Types.REAL: + return Float.class; + case Types.FLOAT: + case Types.DOUBLE: + return Double.class; + case Types.BINARY: // MonetDB currently does not support these + case Types.VARBINARY: // see treat_blob_as_binary property + case Types.LONGVARBINARY: + return byte[].class; + case Types.DATE: + return java.sql.Date.class; + case Types.TIME: + return Time.class; + case Types.TIMESTAMP: + return Timestamp.class; + case Types.CLOB: + return Clob.class; + case Types.BLOB: + return Blob.class; + + // all the rest are currently not implemented and used + default: + return String.class; + } + } + + /** + * Gets the value of the designated column in the current row of this + * ResultSet object as an Object in the Java programming language. + * + * This method will return the value of the given column as a Java object. + * The type of the Java object will be the default Java object type + * corresponding to the column's SQL type, following the mapping for + * built-in types specified in the JDBC specification. If the value is an + * SQL NULL, the driver returns a Java null. + * + * This method may also be used to read database-specific abstract data + * types. + * + * @param columnName the SQL name of the column + * @return a java.lang.Object holding the column value + * @throws SQLException if a database access error occurs + */ + @Override + public Object getObject(String columnName) throws SQLException { + return getObject(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as an Object in the Java programming language. If the + * value is an SQL NULL, the driver returns a Java null. This method uses + * the specified Map object for custom mapping if appropriate. + * + * @param colName the name of the column from which to retrieve the value + * @param map a java.util.Map object that contains the mapping from SQL + * type names to classes in the Java programming language + * @return an Object representing the SQL value in the specified column + * @throws SQLException if a database access error occurs + */ + @Override + public Object getObject(String colName, Map<String,Class<?>> map) throws SQLException { + return getObject(findColumn(colName), map); + } + + @Override + public Ref getRef(int i) throws SQLException { + throw newSQLFeatureNotSupportedException("getRef"); + } + + @Override + public Ref getRef(String colName) throws SQLException { + throw newSQLFeatureNotSupportedException("getRef"); + } + + /** + * Retrieves the current row number. The first row is number 1, the second + * number 2, and so on. + * + * @return the current row number; 0 if there is no current row + */ + @Override + public int getRow() { + return curRow; + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.sql.RowId object in the Java + * programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if there is no such column + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public RowId getRowId(int columnIndex) throws SQLException { + throw newSQLFeatureNotSupportedException("getRowId"); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.sql.RowId object in the Java + * programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if the ResultSet object does not contain columnName + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public RowId getRowId(String columnName) throws SQLException { + throw newSQLFeatureNotSupportedException("getRowId"); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a short in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if there is no such column + */ + @Override + public short getShort(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return 0; + } + lastReadWasNull = false; + try { + return Short.parseShort(val); + } catch (NumberFormatException e) { + // ignore conversion error, return the default: 0 + return 0; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a short in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is 0 + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public short getShort(String columnName) throws SQLException { + return getShort(findColumn(columnName)); + } + + /** + * Retrieves the Statement object that produced this ResultSet object. If + * the result set was generated some other way, such as by a + * DatabaseMetaData method, this method returns null. + * + * @return the Statement object that produced this ResultSet object or null + * if the result set was produced some other way + */ + @Override + public Statement getStatement() { + return statement; + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a String in the Java programming language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if there is no such column + */ + @Override + public String getString(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + return val; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a String in the Java programming language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if the ResultSet object does not contain columnName + */ + @Override + public String getString(String columnName) throws SQLException { + return getString(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a String in the Java programming + * language. It is intended for use when accessing NCHAR,NVARCHAR + * and LONGNVARCHAR columns. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if there is no such column + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public String getNString(int columnIndex) throws SQLException { + return getString(columnIndex); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a String in the Java programming + * language. It is intended for use when accessing NCHAR,NVARCHAR + * and LONGNVARCHAR columns. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if the ResultSet object does not contain columnName + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public String getNString(String columnName) throws SQLException { + return getNString(findColumn(columnName)); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet as a java.sql.SQLXML object in the Java + * programming language. + * + * @param i the first column is 1, the second is 2, ... + * @return a SQLXML object that maps an SQL XML value + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public SQLXML getSQLXML(int i) throws SQLException { + throw newSQLFeatureNotSupportedException("getSQLXML"); + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet as a java.sql.SQLXML object in the Java + * programming language. + * + * @param colName the label for the column specified with the SQL AS + * clause. If the SQL AS clause was not specified, then the + * label is the name of the column + * @return a SQLXML object that maps an SQL XML value + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public SQLXML getSQLXML(String colName) throws SQLException { + throw newSQLFeatureNotSupportedException("getSQLXML"); + } + + // This behaviour is according table B-6 of Sun JDBC Specification 3.0 + private SimpleDateFormat ts = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private SimpleDateFormat t = + new SimpleDateFormat("HH:mm:ss"); + private SimpleDateFormat d = + new SimpleDateFormat("yyyy-MM-dd"); + /** + * Helper method which parses the date/time value for columns of type + * TIME, DATE and TIMESTAMP. For the types CHAR, VARCHAR and + * LONGVARCHAR an attempt is made to parse the date according to the + * given type. The given Calender object is filled with the parsed + * data. Optional fractional seconds (nanos) are returned by this + * method. If the underlying type of the column is none of the + * mentioned six, January 1st 1970 0:00:00 GMT is returned.<br /> + * The dates are parsed with the given Calendar. + * + * @param cal the Calendar to use/fill when parsing the date/time + * @param col the column to parse + * @param type the corresponding java.sql.Types type of the calling + * function + * @return the fractional seconds (nanos) or -1 if the value is NULL + * @throws SQLException if a database error occurs + */ + private int getJavaDate(Calendar cal, int columnIndex, int type) + throws SQLException + { + if (cal == null) throw + new IllegalArgumentException("No Calendar object given!"); + + final String monetDate; + final String MonetDBType; + int JdbcType; + try { + monetDate = tlp.values[columnIndex - 1]; + if (monetDate == null) { + lastReadWasNull = true; + return -1; + } + lastReadWasNull = false; + MonetDBType = types[columnIndex - 1]; + JdbcType = JdbcSQLTypes[columnIndex - 1]; + // If we got a string type, set the JdbcType to the given type + // so we attempt to parse it as the caller thinks it is. + if (JdbcType == Types.CHAR || + JdbcType == Types.VARCHAR || + JdbcType == Types.LONGVARCHAR || + JdbcType == Types.CLOB) + { + JdbcType = type; + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + + // we know whether we have a time with or without + // time zone if the monet type ends with "tz" + boolean hasTimeZone = (MonetDBType != null) ? MonetDBType.endsWith("tz") : false; + TimeZone ptz = cal.getTimeZone(); + + // it is important to parse the time in the given timezone in + // order to get a correct (UTC) time value, hence we need to + // parse it first + if (hasTimeZone) { + // MonetDB/SQL99: Sign TwoDigitHours : Minutes + ptz = TimeZone.getTimeZone("GMT" + + monetDate.substring( + monetDate.length() - 6, + monetDate.length())); + } + + java.util.Date pdate = null; + ParsePosition ppos = new ParsePosition(0); + switch(JdbcType) { + case Types.DATE: + d.setTimeZone(ptz); + pdate = d.parse(monetDate, ppos); + break; + case Types.TIME: + t.setTimeZone(ptz); + pdate = t.parse(monetDate, ppos); + break; + case Types.TIMESTAMP: + ts.setTimeZone(ptz); + pdate = ts.parse(monetDate, ppos); + break; + default: + addWarning("unsupported data type", "01M03"); + cal.clear(); + return 0; + } + if (pdate == null) { + // parsing failed + int epos = ppos.getErrorIndex(); + if (epos == -1) { + addWarning("parsing '" + monetDate + "' failed", "01M10"); + } else if (epos < monetDate.length()) { + addWarning("parsing failed," + + " found: '" + monetDate.charAt(epos) + "'" + + " in: \"" + monetDate + "\"" + + " at pos: " + ppos.getErrorIndex(), "01M10"); + } else { + addWarning("parsing failed, expected more data after '" + + monetDate + "'", "01M10"); + } + // default value + cal.clear(); + return 0; + } + cal.setTime(pdate); + + int nanos = 0; + if (JdbcType == Types.TIME || JdbcType == Types.TIMESTAMP) { + // parse additional nanos (if any) + int pos = ppos.getIndex(); + char[] monDate = monetDate.toCharArray(); + if (pos < monDate.length && monDate[pos] == '.') { + pos++; + int ctr; + try { + nanos = getIntrinsicValue(monDate[pos], pos++); + for (ctr = 1; + pos < monDate.length && + monDate[pos] >= '0' && + monDate[pos] <= '9'; + ctr++) + { + if (ctr < 9) { + nanos *= 10; + nanos += (getIntrinsicValue(monDate[pos], pos)); + } + if (ctr == 2) // we have three at this point + cal.set(Calendar.MILLISECOND, nanos); + pos++; + } + while (ctr++ < 9) + nanos *= 10; + } catch(MCLParseException e) { + addWarning(e.getMessage() + + " found: '" + monDate[e.getErrorOffset()] + "'" + + " in: \"" + monetDate + "\"" + + " at pos: " + e.getErrorOffset(), "01M10"); + // default value + cal.clear(); + nanos = 0; + } + } + } + return nanos; + } + + /** + * Small helper method that returns the intrinsic value of a char if + * it represents a digit. If a non-digit character is encountered + * an MCLParseException is thrown. + * + * @param c the char + * @param pos the position + * @return the intrinsic value of the char + * @throws MCLParseException if c is not a digit + */ + private final static int getIntrinsicValue(char c, int pos) + throws MCLParseException + { + // note: don't use Character.isDigit() here, because + // we only want ISO-LATIN-1 digits + if (c >= '0' && c <= '9') { + return (int)c - (int)'0'; + } else { + throw new MCLParseException("Expected a digit", pos); + } + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Date object in the Java programming + * language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + * @see #getDate(int col, Calendar cal) + */ + @Override + public java.sql.Date getDate(int columnIndex) throws SQLException { + return getDate(columnIndex, Calendar.getInstance()); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Date object in the Java programming + * language. This method uses the given calendar to construct an appropriate + * millisecond value for the date if the underlying database does not store + * timezone information. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @param cal the java.util.Calendar object to use in constructing the date + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public java.sql.Date getDate(int columnIndex, Calendar cal) + throws SQLException + { + int ret = getJavaDate(cal, columnIndex, Types.DATE); + return ret == -1 ? null : new java.sql.Date(cal.getTimeInMillis()); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Date object in the Java programming + * language. + * + * @param columnName the SQL name of the column from which to retrieve the + * value + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public java.sql.Date getDate(String columnName) throws SQLException { + return getDate(findColumn(columnName), Calendar.getInstance()); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Date object in the Java programming + * language. This method uses the given calendar to construct an appropriate + * millisecond value for the date if the underlying database does not store + * timezone information. + * + * @param columnName the SQL name of the column from which to retrieve the + * value + * @param cal the java.util.Calendar object to use in constructing the date + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public java.sql.Date getDate(String columnName, Calendar cal) + throws SQLException + { + return getDate(findColumn(columnName), cal); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Time object in the Java programming + * language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public Time getTime(int columnIndex) throws SQLException { + return getTime(columnIndex, Calendar.getInstance()); + } + + /** + * Retrieves the value of the designated column in the current row of + * this ResultSet object as a java.sql.Time object in the Java programming + * language. This method uses the given calendar to construct an appropriate + * millisecond value for the time if the underlying database does not store + * timezone information. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @param cal the java.util.Calendar object to use in constructing the + * timestamp + * @return the column value as a java.sql.Timestamp object; if the value is + * SQL NULL, the value returned is null in the Java programming + * language + * @throws SQLException if a database access error occurs + */ + @Override + public Time getTime(int columnIndex, Calendar cal) + throws SQLException + { + int ret = getJavaDate(cal, columnIndex, Types.TIME); + return ret == -1 ? null : new Time(cal.getTimeInMillis()); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Time object in the Java programming + * language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public Time getTime(String columnName) throws SQLException { + return getTime(findColumn(columnName), Calendar.getInstance()); + } + + /** + * Retrieves the value of the designated column in the current row of + * this ResultSet object as a java.sql.Time object in the Java programming + * language. This method uses the given calendar to construct an appropriate + * millisecond value for the time if the underlying database does not store + * timezone information. + * + * @param columnName the SQL name of the column + * @param cal the java.util.Calendar object to use in constructing the + * timestamp + * @return the column value as a java.sql.Timestamp object; if the value is + * SQL NULL, the value returned is null in the Java programming + * language + * @throws SQLException if a database access error occurs + */ + @Override + public Time getTime(String columnName, Calendar cal) + throws SQLException + { + return getTime(findColumn(columnName), cal); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Timestamp object in the Java programming + * language. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException { + return getTimestamp(columnIndex, Calendar.getInstance()); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Timestamp object in the Java programming + * language. This method uses the given calendar to construct an appropriate + * millisecond value for the timestamp if the underlying database does not + * store timezone information. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @param cal the java.util.Calendar object to use in constructing the + * timestamp + * @return the column value as a java.sql.Timestamp object; if the value is + * SQL NULL, the value returned is null in the Java programming + * language + * @throws SQLException if a database access error occurs + */ + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) + throws SQLException + { + int nanos = getJavaDate(cal, columnIndex, Types.TIMESTAMP); + if (nanos == -1) + return null; + + Timestamp ts = new Timestamp(cal.getTimeInMillis()); + ts.setNanos(nanos); + return ts; + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Timestamp object in the Java programming + * language. + * + * @param columnName the SQL name of the column + * @return the column value; if the value is SQL NULL, the value returned + * is null + * @throws SQLException if a database access error occurs + */ + @Override + public Timestamp getTimestamp(String columnName) throws SQLException { + return getTimestamp(findColumn(columnName), Calendar.getInstance()); + } + + /** + * Retrieves the value of the designated column in the current row of this + * ResultSet object as a java.sql.Timestamp object in the Java programming + * language. This method uses the given calendar to construct an appropriate + * millisecond value for the timestamp if the underlying database does not + * store timezone information. + * + * @param columnName the SQL name of the column + * @param cal the java.util.Calendar object to use in constructing the + * timestamp + * @return the column value as a java.sql.Timestamp object; if the value is + * SQL NULL, the value returned is null in the Java programming + * language + * @throws SQLException if a database access error occurs + */ + @Override + public Timestamp getTimestamp(String columnName, Calendar cal) + throws SQLException + { + return getTimestamp(findColumn(columnName), cal); + } + + /** + * Retrieves the type of this ResultSet object. The type is determined by + * the Statement object that created the result set. + * + * @return ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, + * or ResultSet.TYPE_SCROLL_SENSITIVE + */ + @Override + public int getType() { + return type; + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.net.URL object in the Java + * programming language. + * + * @param columnIndex the index of the column 1 is the first, + * 2 is the second,... + * @return the column value as a java.net.URL object; if the value + * is SQL NULL, the value returned is null in the Java + * programming language + * @throws SQLException if a database access error occurs, or if a + * URL is malformed + */ + @Override + public URL getURL(int columnIndex) throws SQLException { + try { + String val = tlp.values[columnIndex - 1]; + if (val == null) { + lastReadWasNull = true; + return null; + } + lastReadWasNull = false; + try { + return new URL(val); + } catch (MalformedURLException e) { + throw new SQLException(e.getMessage(), "M1M05"); + } + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidColumnIndexException(columnIndex); + } + } + + /** + * Retrieves the value of the designated column in the current row + * of this ResultSet object as a java.net.URL object in the Java + * programming language. + * + * @param columnName the SQL name of the column + * @return the column value as a java.net.URL object; if the value + * is SQL NULL, the value returned is null in the Java + * programming language + * @throws SQLException if a database access error occurs, or if a + * URL is malformed + */ + @Override + public URL getURL(String columnName) throws SQLException { + return getURL(findColumn(columnName)); + } + + /** + * Retrieves the first warning reported by calls on this ResultSet object. + * If there is more than one warning, subsequent warnings will be chained to + * the first one and can be retrieved by calling the method + * SQLWarning.getNextWarning on the warning that was retrieved previously. + * + * This method may not be called on a closed result set; doing so will cause + * an SQLException to be thrown. + * + * Note: Subsequent warnings will be chained to this SQLWarning. + * + * @return the first SQLWarning object or null if there are none + * @throws SQLException if a database access error occurs or this method is + * called on a closed connection + */ + @Override + public SQLWarning getWarnings() throws SQLException { + if (header != null && header.isClosed()) + throw new SQLException("Cannot call on closed ResultSet", "M1M20"); + + // if there are no warnings, this will be null, which fits with the + // specification. + return warnings; + } + + /** + * Retrieves whether the cursor is after the last row in this ResultSet + * object. + * + * @return true if the cursor is after the last row; false if the cursor is + * at any other position or the result set contains no rows + */ + @Override + public boolean isAfterLast() { + return curRow == tupleCount + 1; + } + + /** + * Retrieves whether the cursor is before the first row in this ResultSet + * object. + * + * @return true if the cursor is before the first row; false if the cursor + * is at any other position or the result set contains no rows + */ + @Override + public boolean isBeforeFirst() { + return curRow == 0; + } + + /** + * Retrieves whether this ResultSet object has been closed. A + * ResultSet is closed if the method close has been called on it, or + * if it is automatically closed. + * + * @return true if this ResultSet object is closed; false if it is + * still open + */ + @Override + public boolean isClosed() { + return header != null && header.isClosed(); + } + + /** + * Retrieves whether the cursor is on the first row of this ResultSet + * object. + * + * @return true if the cursor is on the first row; false otherwise + */ + @Override + public boolean isFirst() { + return curRow == 1; + } + + /** + * Retrieves whether the cursor is on the last row of this ResultSet object. + * + * @return true if the cursor is on the last row; false otherwise + */ + @Override + public boolean isLast() { + return curRow == tupleCount; + } + + /** + * Moves the cursor to the last row in this ResultSet object. + * + * @return true if the cursor is on a valid row; false if there are no rows + * in the result set + * @throws SQLException if a database access error occurs or the result set + * type is TYPE_FORWARD_ONLY + */ + @Override + public boolean last() throws SQLException { + return absolute(-1); + } + + /** + * Moves the cursor down one row from its current position. A ResultSet + * cursor is initially positioned before the first row; the first call to + * the method next makes the first row the current row; the second call + * makes the second row the current row, and so on. + * + * If an input stream is open for the current row, a call to the method + * next will implicitly close it. A ResultSet object's warning chain is + * cleared when a new row is read. + * + * @return true if the new current row is valid; false if there are no + * more rows + * @throws SQLException if a database access error occurs or ResultSet is + * closed + */ + @Override + public boolean next() throws SQLException { + return relative(1); + } + + /** + * Moves the cursor to the previous row in this ResultSet object. + * + * @return true if the cursor is on a valid row; false if it is off + * the result set + * @throws SQLException if a database access error occurs or ResultSet is + * closed or the result set type is TYPE_FORWARD_ONLY + */ + @Override + public boolean previous() throws SQLException { + return relative(-1); + } + + /** + * Moves the cursor a relative number of rows, either positive or negative. + * Attempting to move beyond the first/last row in the result set positions + * the cursor before/after the the first/last row. Calling relative(0) is + * valid, but does not change the cursor position. + * + * Note: Calling the method relative(1) is identical to calling the method + * next() and calling the method relative(-1) is identical to calling the + * method previous(). + * + * @param rows an int specifying the number of rows to move from the current + * row; a positive number moves the cursor forward; a negative number + * moves the cursor backward + * @return true if the cursor is on a row; false otherwise + * @throws SQLException if a database access error occurs, there is no current + * row, or the result set type is TYPE_FORWARD_ONLY + */ + @Override + public boolean relative(int rows) throws SQLException { + return absolute(curRow + rows); + } + + /** + * Retrieves whether a row has been deleted. A deleted row may leave a visible "hole" in a result set. + * This method can be used to detect holes in a result set. + * The value returned depends on whether or not this ResultSet object can detect deletions. + * + * Note: Support for the rowDeleted method is optional with a result set concurrency of CONCUR_READ_ONLY + * + * Returns: true if the current row is detected to have been deleted by the owner or another; false otherwise + * + * Throws: + * SQLException - if a database access error occurs or this method is called on a closed result set + * SQLFeatureNotSupportedException - if the JDBC driver does not support this method + * Since: 1.2 + * See Also: DatabaseMetaData.deletesAreDetected(int) + */ + @Override + public boolean rowDeleted() throws SQLException { + return false; + } + + /** + * Retrieves whether the current row has had an insertion. + * The value returned depends on whether or not this ResultSet object can detect visible inserts. + * + * Note: Support for the rowInserted method is optional with a result set concurrency of CONCUR_READ_ONLY + * + * Returns: true if the current row is detected to have been inserted; false otherwise + * + * Throws: + * SQLException - if a database access error occurs or this method is called on a closed result set + * SQLFeatureNotSupportedException - if the JDBC driver does not support this method + * Since: 1.2 + * See Also: DatabaseMetaData.insertsAreDetected(int) + */ + @Override + public boolean rowInserted() throws SQLException { + return false; + } + + /** + * Retrieves whether the current row has been updated. + * The value returned depends on whether or not the result set can detect updates. + * + * Note: Support for the rowUpdated method is optional with a result set concurrency of CONCUR_READ_ONLY + * + * Returns: true if the current row is detected to have been visibly updated by the owner or another; false otherwise + * + * Throws: + * SQLException - if a database access error occurs or this method is called on a closed result set + * SQLFeatureNotSupportedException - if the JDBC driver does not support this method + * Since: 1.2 + * See Also: DatabaseMetaData.updatesAreDetected(int) + */ + @Override + public boolean rowUpdated() throws SQLException { + return false; + } + + /* the next methods are all related to updateable result sets, which we + currently do not support */ + @Override + public void cancelRowUpdates() throws SQLException { + throw newSQLFeatureNotSupportedException("cancelRowUpdates"); + } + + @Override + public void deleteRow() throws SQLException { + throw newSQLFeatureNotSupportedException("deleteRow"); + } + + @Override + public void insertRow() throws SQLException { + throw newSQLFeatureNotSupportedException("insertRow"); + } + + @Override + public void moveToCurrentRow() throws SQLException { + throw newSQLFeatureNotSupportedException("moveToCurrentRow"); + } + + @Override + public void moveToInsertRow() throws SQLException { + throw newSQLFeatureNotSupportedException("moveToInsertRow"); + } + + @Override + public void refreshRow() throws SQLException { + throw newSQLFeatureNotSupportedException("refreshRow"); + } + + + @Override + public void updateArray(int columnIndex, Array x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateArray"); + } + + @Override + public void updateArray(String columnName, Array x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateArray"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream xh) throws SQLException { + throw newSQLFeatureNotSupportedException("updateAsciiStream"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateAsciiStream"); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateAsciiStream"); + } + + @Override + public void updateAsciiStream(String columnName, InputStream x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateAsciiStream"); + } + + @Override + public void updateAsciiStream(String columnName, InputStream x, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateAsciiStream"); + } + + @Override + public void updateAsciiStream(String columnName, InputStream x, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateAsciiStream"); + } + + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBigDecimal"); + } + + @Override + public void updateBigDecimal(String columnName, BigDecimal x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBigDecimal"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBinaryStream"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBinaryStream"); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBinaryStream"); + } + + @Override + public void updateBinaryStream(String columnName, InputStream x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBinaryStream"); + } + + @Override + public void updateBinaryStream(String columnName, InputStream x, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBinaryStream"); + } + + @Override + public void updateBinaryStream(String columnName, InputStream x, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBinaryStream"); + } + + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBlob"); + } + + @Override + public void updateBlob(int columnIndex, InputStream s) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBlob"); + } + + @Override + public void updateBlob(int columnIndex, InputStream s, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBlob"); + } + + @Override + public void updateBlob(String columnName, Blob x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBlob"); + } + + @Override + public void updateBlob(String columnName, InputStream s) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBlob"); + } + + @Override + public void updateBlob(String columnName, InputStream s, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBlob"); + } + + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBoolean"); + } + + @Override + public void updateBoolean(String columnName, boolean x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBoolean"); + } + + @Override + public void updateByte(int columnIndex, byte x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateByte"); + } + + @Override + public void updateByte(String columnName, byte x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateByte"); + } + + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBytes"); + } + + @Override + public void updateBytes(String columnName, byte[] x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateBytes"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateCharacterStream"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateCharacterStream"); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateCharacterStream"); + } + + @Override + public void updateCharacterStream(String columnName, Reader reader) throws SQLException { + throw newSQLFeatureNotSupportedException("updateCharacterStream"); + } + + @Override + public void updateCharacterStream(String columnName, Reader reader, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateCharacterStream"); + } + + @Override + public void updateCharacterStream(String columnName, Reader reader, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateCharacterStream"); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNCharacterStream"); + } + + public void updateNCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNCharacterStream"); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNCharacterStream"); + } + + @Override + public void updateNCharacterStream(String columnName, Reader reader) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNCharacterStream"); + } + + public void updateNCharacterStream(String columnName, Reader reader, int length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNCharacterStream"); + } + + @Override + public void updateNCharacterStream(String columnName, Reader reader, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNCharacterStream"); + } + + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateClob"); + } + + @Override + public void updateClob(int columnIndex, Reader r) throws SQLException { + throw newSQLFeatureNotSupportedException("updateClob"); + } + + @Override + public void updateClob(int columnIndex, Reader r, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateClob"); + } + + @Override + public void updateClob(String columnName, Clob x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateClob"); + } + + @Override + public void updateClob(String columnName, Reader r) throws SQLException { + throw newSQLFeatureNotSupportedException("updateClob"); + } + + @Override + public void updateClob(String columnName, Reader r, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateClob"); + } + + @Override + public void updateNClob(int columnIndex, NClob x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNClob"); + } + + @Override + public void updateNClob(int columnIndex, Reader r) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNClob"); + } + + @Override + public void updateNClob(int columnIndex, Reader r, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNClob"); + } + + @Override + public void updateNClob(String columnName, NClob x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNClob"); + } + + @Override + public void updateNClob(String columnName, Reader r) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNClob"); + } + + @Override + public void updateNClob(String columnName, Reader r, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNClob"); + } + + @Override + public void updateDate(int columnIndex, java.sql.Date x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateDate"); + } + + @Override + public void updateDate(String columnName, java.sql.Date x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateDate"); + } + + @Override + public void updateDouble(int columnIndex, double x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateDouble"); + } + + @Override + public void updateDouble(String columnName, double x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateDouble"); + } + + @Override + public void updateFloat(int columnIndex, float x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateFloat"); + } + + @Override + public void updateFloat(String columnName, float x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateFloat"); + } + + @Override + public void updateInt(int columnIndex, int x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateInt"); + } + + @Override + public void updateInt(String columnName, int x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateInt"); + } + + @Override + public void updateLong(int columnIndex, long x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateLong"); + } + + @Override + public void updateLong(String columnName, long x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateLong"); + } + + @Override + public void updateNull(int columnIndex) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNull"); + } + + @Override + public void updateNull(String columnName) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNull"); + } + + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateObject"); + } + + @Override + public void updateObject(int columnIndex, Object x, int scale) throws SQLException { + throw newSQLFeatureNotSupportedException("updateObject"); + } + + @Override + public void updateObject(String columnName, Object x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateObject"); + } + + @Override + public void updateObject(String columnName, Object x, int scale) throws SQLException { + throw newSQLFeatureNotSupportedException("updateObject"); + } + + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateRef"); + } + + @Override + public void updateRef(String columnName, Ref x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateRef"); + } + + @Override + public void updateRow() throws SQLException { + throw newSQLFeatureNotSupportedException("updateRow"); + } + + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateRowId"); + } + + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateRowId"); + } + + @Override + public void updateShort(int columnIndex, short x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateShort"); + } + + @Override + public void updateShort(String columnName, short x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateShort"); + } + + @Override + public void updateString(int columnIndex, String x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateString"); + } + + @Override + public void updateString(String columnName, String x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateString"); + } + + @Override + public void updateNString(int columnIndex, String x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNString"); + } + + @Override + public void updateNString(String columnName, String x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateNString"); + } + + @Override + public void updateSQLXML(String columnName, SQLXML x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateSQLXML"); + } + + @Override + public void updateSQLXML(int columnIndex, SQLXML x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateSQLXML"); + } + + @Override + public void updateTime(int columnIndex, Time x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateTime"); + } + + @Override + public void updateTime(String columnName, Time x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateTime"); + } + + @Override + public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateTimestamp"); + } + + @Override + public void updateTimestamp(String columnName, Timestamp x) throws SQLException { + throw newSQLFeatureNotSupportedException("updateTimestamp"); + } + + // Chapter 14.2.3.3 Sun JDBC 3.0 Specification + /** + * Reports whether the last column read had a value of SQL NULL. Note that + * you must first call one of the getter methods on a column to try to read + * its value and then call the method wasNull to see if the value read was + * SQL NULL. + * + * @return true if the last column value read was SQL NULL and false + * otherwise + */ + @Override + public boolean wasNull() { + return lastReadWasNull; + } + + //== end methods of interface ResultSet + + /** + * Adds a warning to the pile of warnings this ResultSet object has. If + * there were no warnings (or clearWarnings was called) this warning will + * be the first, otherwise this warning will get appended to the current + * warning. + * + * @param reason the warning message + */ + private void addWarning(String reason, String sqlstate) { + if (warnings == null) { + warnings = new SQLWarning(reason, sqlstate); + } else { + warnings.setNextWarning(new SQLWarning(reason, sqlstate)); + } + } + + /** + * Small helper method that formats the "Invalid Column Index number ..." message + * and creates a new SQLException object whose SQLState is set to "M1M05". + * + * @param name the method name + * @return a new created SQLException object with SQLState M1M05 + */ + private final static SQLException newSQLInvalidColumnIndexException(int colIdx) { + return new SQLException("Invalid Column Index number: " + colIdx, "M1M05"); + } + + /** + * Small helper method that formats the "Method ... not implemented" message + * and creates a new SQLFeatureNotSupportedException object + * whose SQLState is set to "0A000". + * + * @param name the method name + * @return a new created SQLFeatureNotSupportedException object with SQLState 0A000 + */ + private final static SQLFeatureNotSupportedException newSQLFeatureNotSupportedException(String name) { + return new SQLFeatureNotSupportedException("Method " + name + " not implemented", "0A000"); + } +} +