Mercurial > hg > monetdb-java
view src/main/java/org/monetdb/jdbc/MonetResultSet.java @ 554:9fa67487f38a onclient
Doc comment fixes
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Thu, 16 Sep 2021 10:30:14 +0200 (2021-09-16) |
parents | 849af4b76b28 |
children | 6aa38e8c0f2d |
line wrap: on
line source
/* * 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 - 2021 MonetDB B.V. */ package org.monetdb.jdbc; import org.monetdb.mcl.parser.MCLParseException; import org.monetdb.mcl.parser.TupleLineParser; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; 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.SQLDataException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLInput; import java.sql.SQLType; // new as of Java 1.8 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.SimpleDateFormat; import java.util.Calendar; import java.util.Map; import java.util.TimeZone; /** *<pre> * A {@link 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. *</pre> * * @author Fabian Groffen * @author Martin van Dinther * @version 1.0 */ public class MonetResultSet extends MonetWrapper implements ResultSet, AutoCloseable { static final int DEF_RESULTSETTYPE = ResultSet.TYPE_FORWARD_ONLY; static final int DEF_FETCHDIRECTION = ResultSet.FETCH_FORWARD; static final int DEF_CONCURRENCY = ResultSet.CONCUR_READ_ONLY; static final int DEF_HOLDABILITY = ResultSet.HOLD_CURSORS_OVER_COMMIT; /** The parental Statement object */ private final Statement statement; /** A Header to retrieve lines from. Note: it will be null in case of a MonetVirtualResultSet ! */ 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 following have protected access modifier for the MonetVirtualResultSet subclass // they are accessed from MonetVirtualResultSet.absolute() /** The current line of the buffer split in columns */ protected final TupleLineParser tlp; /** The number of rows in this ResultSet */ protected final long tupleCount; /** The current position of the cursor for this ResultSet object */ protected int curRow; /** The type of this ResultSet (forward or scrollable) */ private int type = DEF_RESULTSETTYPE; /** The concurrency of this ResultSet (currently only read-only) */ private int concurrency = DEF_CONCURRENCY; /** The warnings for this ResultSet object */ private SQLWarning warnings; /** whether the last read field (via some getXyz() method) was NULL */ private boolean lastReadWasNull = true; /** to 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 IllegalArgumentException if called with null or invalid value for one of the arguments */ MonetResultSet( final Statement statement, final MonetConnection.ResultSetResponse header) throws IllegalArgumentException { if (statement == null) { throw new IllegalArgumentException("Statement may not be null!"); } if (header == null) { throw new IllegalArgumentException("Header may not be null!"); } this.statement = statement; this.header = header; type = header.getRSType(); concurrency = header.getRSConcur(); /* the fetchSize used for this result set is the header's cacheSize */ fetchSize = header.getCacheSize(); columns = header.getNames(); types = header.getTypes(); if (columns == null || types == null) { throw new IllegalArgumentException("Missing Header metadata"); } if (columns.length != types.length) { throw new IllegalArgumentException("Inconsistent Header metadata"); } tupleCount = header.tuplecount; // create result array tlp = new TupleLineParser(columns.length); // for efficiency derive the JDBC SQL type codes from the types[] names once 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 IllegalArgumentException if called with null or invalid value for one of the arguments */ MonetResultSet( final Statement statement, final String[] columns, final String[] types, final int results) throws IllegalArgumentException { if (statement == null || columns == null || types == null) { throw new IllegalArgumentException("One of the given arguments is null"); } if (columns.length != types.length) { throw new IllegalArgumentException("Given arrays are not the same size"); } if (results < 0) { throw new IllegalArgumentException("Negative rowcount not allowed"); } this.statement = statement; header = null; fetchSize = 0; this.columns = columns; this.types = types; tupleCount = results; tlp = new TupleLineParser(columns.length); // for efficiency derive the JDBC SQL type codes from the types[] names once 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 final void populateJdbcSQLtypesArray() { MonetConnection connection = null; try { connection = (MonetConnection) statement.getConnection(); } catch (SQLException se) { /* ignore it */ } for (int i = 0; i < types.length; i++) { int javaSQLtype = MonetDriver.getJdbcSQLType(types[i]); if (javaSQLtype == Types.CLOB) { if (connection != null && connection.mapClobAsVarChar()) javaSQLtype = Types.VARCHAR; } else if (javaSQLtype == Types.BLOB) { if (connection != null && connection.mapBlobAsVarBinary()) javaSQLtype = Types.VARBINARY; } JdbcSQLTypes[i] = javaSQLtype; } } //== 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 { checkNotClosed(); if (row != curRow + 1 && type == TYPE_FORWARD_ONLY) throw new SQLException("(Absolute) positioning not allowed on forward only result sets!", "M1M05"); // first calculate what the JDBC row is if (row < 0) { // calculate the negatives... row = (int) 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 = (int) tupleCount + 1; // after last // store it curRow = row; if (header == null) return false; final String tmpLine = header.getLine(row - 1); 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((int)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 columnLabel the name of the column * @return the column index of the given column name * @throws SQLException if the ResultSet object does not contain a column labeled columnLabel, * a database access error occurs or this method is called on a closed result set */ @Override public int findColumn(final String columnLabel) throws SQLException { checkNotClosed(); if (columnLabel != null) { final int array_size = columns.length; for (int i = 0; i < array_size; i++) { if (columnLabel.equals(columns[i])) return i + 1; } /* if an exact match did not succeed try a case insensitive match */ for (int i = 0; i < array_size; i++) { if (columnLabel.equalsIgnoreCase(columns[i])) return i + 1; } } throw new SQLException("No such column name: " + columnLabel, "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(final int columnIndex) throws SQLException { throw newSQLFeatureNotSupportedException("getArray"); } @Override public Array getArray(final String columnLabel) 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(final int columnIndex) throws SQLException { throw newSQLFeatureNotSupportedException("getAsciiStream"); } @Override public InputStream getAsciiStream(final String columnLabel) throws SQLException { throw newSQLFeatureNotSupportedException("getAsciiStream"); } @Override @Deprecated public InputStream getUnicodeStream(int columnIndex) throws SQLException { throw newSQLFeatureNotSupportedException("getUnicodeStream"); } @Override @Deprecated public InputStream getUnicodeStream(String columnLabel) 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. * * 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(final int columnIndex) throws SQLException { checkNotClosed(); try { switch (JdbcSQLTypes[columnIndex - 1]) { case Types.BLOB: final Blob blob = getBlob(columnIndex); if (blob == null) return null; return blob.getBinaryStream(); case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: final byte[] bte = getBytes(columnIndex); if (bte == null) return null; return new java.io.ByteArrayInputStream(bte); } throw new SQLException("Cannot operate on type: " + types[columnIndex - 1], "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. * * 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 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 * @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(final String columnLabel) throws SQLException { return getBinaryStream(findColumn(columnLabel)); } /** * 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 or this method is called on a closed result set */ @Override public Reader getCharacterStream(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; return new java.io.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 columnLabel 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(final String columnLabel) throws SQLException { return getCharacterStream(findColumn(columnLabel)); } /** * 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 */ @Override public Reader getNCharacterStream(final int columnIndex) throws SQLException { return getCharacterStream(columnIndex); } /** * 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 columnLabel 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 getNCharacterStream(final String columnLabel) throws SQLException { return getCharacterStream(findColumn(columnLabel)); } /** * 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 columnIndex 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 or this method is called on a closed result set */ @Override public Blob getBlob(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; return new MonetBlob(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 columnLabel 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(final String columnLabel) throws SQLException { return getBlob(findColumn(columnLabel)); } /** * 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 columnIndex 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 or this method is called on a closed result set */ @Override public Clob getClob(final int columnIndex) throws SQLException { checkNotClosed(); try { final 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 columnLabel 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(final String columnLabel) throws SQLException { return getClob(findColumn(columnLabel)); } /** * 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 columnIndex 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(final int columnIndex) 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 columnLabel 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(final String columnLabel) 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 or this method is called on a closed result set */ @Override public BigDecimal getBigDecimal(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; return new BigDecimal(val); } catch (NumberFormatException e) { throw newSQLNumberFormatException(e); } 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 or this method is called on a closed result set */ @Override @Deprecated public BigDecimal getBigDecimal(final int columnIndex, final int scale) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; return (new BigDecimal(val)).setScale(scale); } catch (NumberFormatException e) { throw newSQLNumberFormatException(e); } 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 columnLabel 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(final String columnLabel) throws SQLException { return getBigDecimal(findColumn(columnLabel)); } /** * Retrieves the value of the designated column in the current row of this * ResultSet object as a java.math.BigDecimal with full precision. * * @param columnLabel 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(final String columnLabel, final int scale) throws SQLException { return getBigDecimal(findColumn(columnLabel), 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 the columnIndex is not valid; if a database access error occurs * or this method is called on a closed result set */ @Override public boolean getBoolean(final int columnIndex) throws SQLException { checkNotClosed(); try { final 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.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 columnLabel 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 columnLabel */ @Override public boolean getBoolean(final String columnLabel) throws SQLException { return getBoolean(findColumn(columnLabel)); } /** * 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 or this method is called on a closed result set */ @Override public byte getByte(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return (byte) 0; } lastReadWasNull = false; return Byte.parseByte(val); } catch (NumberFormatException e) { throw newSQLNumberFormatException(e); } 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 columnLabel 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(final String columnLabel) throws SQLException { return getByte(findColumn(columnLabel)); } /** * 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 or this method is called on a closed result set */ @Override public byte[] getBytes(final int columnIndex) throws SQLException { checkNotClosed(); try { final 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: return MonetBlob.hexStrToByteArray(val); default: throw new SQLException("Cannot operate on type: " + types[columnIndex - 1], "M1M05"); } } catch (NumberFormatException e) { throw newSQLNumberFormatException(e); } 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 columnLabel 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(final String columnLabel) throws SQLException { return getBytes(findColumn(columnLabel)); } /** * 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 or this method is called on a closed result set */ @Override public double getDouble(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return 0; } lastReadWasNull = false; return Double.parseDouble(val); } catch (NumberFormatException e) { throw newSQLNumberFormatException(e); } 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 columnLabel 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 columnLabel */ @Override public double getDouble(final String columnLabel) throws SQLException { return getDouble(findColumn(columnLabel)); } /** * 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. * * @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. * * @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(final 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(final 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 or this method is called on a closed result set */ @Override public float getFloat(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return 0; } lastReadWasNull = false; return Float.parseFloat(val); } catch (NumberFormatException e) { throw newSQLNumberFormatException(e); } 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 columnLabel 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 columnLabel */ @Override public float getFloat(final String columnLabel) throws SQLException { return getFloat(findColumn(columnLabel)); } /** * 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 or this method is called on a closed result set */ @Override public int getInt(final int columnIndex) throws SQLException { checkNotClosed(); String val = ""; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return 0; } lastReadWasNull = false; return Integer.parseInt(val); } catch (NumberFormatException e) { // The oid datatype values (as string) have a @0 suffix in the string value. // To allow succesful parsing and conversion to int, we need to remove the suffix first if ("oid".equals(types[columnIndex - 1])) { if (val.endsWith("@0")) { try { return Integer.parseInt(val.substring(0, val.length()-2)); } catch (NumberFormatException nfe) { throw newSQLNumberFormatException(nfe); } } } throw newSQLNumberFormatException(e); } 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 columnLabel 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 columnLabel */ @Override public int getInt(final String columnLabel) throws SQLException { return getInt(findColumn(columnLabel)); } /** * 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 or this method is called on a closed result set */ @Override public long getLong(final int columnIndex) throws SQLException { checkNotClosed(); String val = ""; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return 0; } lastReadWasNull = false; return Long.parseLong(val); } catch (NumberFormatException e) { // 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 the suffix first if ("oid".equals(types[columnIndex - 1])) { if (val.endsWith("@0")) { try { return Long.parseLong(val.substring(0, val.length()-2)); } catch (NumberFormatException nfe) { throw newSQLNumberFormatException(nfe); } } } throw newSQLNumberFormatException(e); } 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 columnLabel 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 columnLabel */ @Override public long getLong(final String columnLabel) throws SQLException { return getLong(findColumn(columnLabel)); } /* 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() throws SQLException { // return inner class which implements the ResultSetMetaData interface return new rsmdw() { private final String[] schemas = (header != null) ? header.getSchemaNames() : null; private final String[] tables = (header != null) ? header.getTableNames() : null; private final MonetConnection conn = (MonetConnection)getStatement().getConnection(); // for the methods: getPrecision(), getScale(), isNullable() and isAutoIncrement(), we use // caches to store precision, scale, isNullable and isAutoincrement values for each resultset column // so they do not need to queried and fetched from the server again and again. private final int array_size = columns.length + 1; // add 1 as in JDBC columns start from 1 (array from 0). private final boolean[] _is_queried = new boolean[array_size]; private final boolean[] _is_fetched = new boolean[array_size]; private final int[] _precision = new int[array_size]; private final int[] _scale = new int[array_size]; private final int[] _isNullable = new int[array_size]; private final boolean[] _isAutoincrement = new boolean[array_size]; /** * A private utility method to check validity of column index number * @throws SQLDataException when invalid column index number */ private final void checkColumnIndexValidity(final int column) throws SQLDataException { if (column < 1 || column > columns.length) throw MonetResultSet.newSQLInvalidColumnIndexException(column); } /** * A private method to fetch the precision, scale, isNullable and isAutoincrement values * combined for a specific column. * The fetched values are stored in the above array caches. */ private final void fetchColumnInfo(final int column) throws SQLException { // for debug: System.out.println("fetchColumnInfo(" + column + ")"); checkColumnIndexValidity(column); if (_is_fetched[column] != true) { // fetch column info for multiple columns combined in one go, starting at 1 fetchManyColumnsInfo(1); if (_is_fetched[column] != true) { // fetch info for column x if it was not fetched by the previous call fetchManyColumnsInfo(column); } } if (_is_fetched[column]) return; // apparently no data could be fetched for this resultset column, fall back to defaults _precision[column] = 0; _scale[column] = 0; _isNullable[column] = columnNullableUnknown; _isAutoincrement[column] = false; } /** * A private method to fetch the precision, scale, isNullable and isAutoincrement values * for many fully qualified columns combined in one SQL query to reduce the number of queries sent. * As fetching this meta information from the server per column is costly we combine the querying of * the precision, scale, isNullable and isAutoincrement values and cache it in internal arrays. * We also do this for many (up to 50) columns combined in one query to reduce * the number of queries needed for fetching this metadata for all resultset columns. * Many generic JDBC database tools (e.g. SQuirreL) request this meta data for each column of each resultset, * so these optimisations reduces the number of meta data queries significantly. */ private final void fetchManyColumnsInfo(final int column) throws SQLException { // for debug: System.out.println("fetchManyColumnsInfo(" + column + ")"); // Most queries have less than 50 resultset columns // So 50 is a good balance between speedup (up to 49x) and size of query sent to server final int MAX_COLUMNS_PER_QUERY = 50; final StringBuilder query = new StringBuilder(600 + (MAX_COLUMNS_PER_QUERY * 150)); /* next SQL query is a simplified version of query in MonetDatabaseMetaData.getColumns(), to fetch only the needed attributes of a column */ query.append("SELECT " + "s.\"name\" AS schnm, " + "t.\"name\" AS tblnm, " + "c.\"name\" AS colnm, " + "c.\"type_digits\", " + "c.\"type_scale\", " + "cast(CASE c.\"null\" WHEN true THEN ").append(ResultSetMetaData.columnNullable) .append(" WHEN false THEN ").append(ResultSetMetaData.columnNoNulls) .append(" ELSE ").append(ResultSetMetaData.columnNullableUnknown) .append(" END AS int) AS nullable, ").append( "cast(CASE WHEN c.\"default\" IS NOT NULL AND c.\"default\" LIKE 'next value for %' THEN true ELSE false END AS boolean) AS isautoincrement " + "FROM \"sys\".\"columns\" c " + "JOIN \"sys\".\"tables\" t ON c.\"table_id\" = t.\"id\" " + "JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " + "WHERE "); /* combine the conditions for multiple (up to 50) columns into the WHERE-clause */ String schName = null; String tblName = null; String colName = null; int queriedcolcount = 0; for (int col = column; col < array_size && queriedcolcount < MAX_COLUMNS_PER_QUERY; col++) { if (_is_fetched[col] != true) { if (_is_queried[col] != true) { _precision[col] = 0; _scale[col] = 0; _isNullable[col] = columnNullableUnknown; _isAutoincrement[col] = false; schName = getSchemaName(col); if (schName != null && !schName.isEmpty()) { tblName = getTableName(col); if (tblName != null && !tblName.isEmpty()) { colName = getColumnName(col); if (colName != null && !colName.isEmpty()) { if (queriedcolcount > 0) query.append(" OR "); query.append("(s.\"name\" = ").append(MonetWrapper.sq(schName)); query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(tblName)); query.append(" AND c.\"name\" = ").append(MonetWrapper.sq(colName)); query.append(")"); _is_queried[col] = true; // flag it queriedcolcount++; } } } if (_is_queried[col] != true) { // make sure we do not try to query it again next time as it is not queryable _is_fetched[col] = true; } } } } if (queriedcolcount == 0) return; // execute query to get information on queriedcolcount (or less) columns. final Statement stmt = conn.createStatement(); if (stmt != null) { // for debug: System.out.println("SQL (len " + query.length() + "): " + query.toString()); final ResultSet rs = stmt.executeQuery(query.toString()); if (rs != null) { String rsSchema = null; String rsTable = null; String rsColumn = null; while (rs.next()) { rsSchema = rs.getString(1); // col 1 is schnm rsTable = rs.getString(2); // col 2 is tblnm rsColumn = rs.getString(3); // col 3 is colnm // find the matching schema.table.column entry in the array for (int col = 1; col < array_size; col++) { if (_is_fetched[col] != true && _is_queried[col]) { colName = getColumnName(col); if (colName != null && colName.equals(rsColumn)) { tblName = getTableName(col); if (tblName != null && tblName.equals(rsTable)) { schName = getSchemaName(col); if (schName != null && schName.equals(rsSchema)) { // found matching entry // for debug: System.out.println("Found match at [" + col + "] for " + schName + "." + tblName + "." + colName); _precision[col] = rs.getInt(4); // col 4 is "type_digits" (or "COLUMN_SIZE") _scale[col] = rs.getInt(5); // col 5 is "type_scale" (or "DECIMAL_DIGITS") _isNullable[col] = rs.getInt(6); // col 6 is nullable (or "NULLABLE") _isAutoincrement[col] = rs.getBoolean(7); // col 7 is isautoincrement (or "IS_AUTOINCREMENT") _is_fetched[col] = true; queriedcolcount--; // we found the match, exit the for-loop col = array_size; } } } } } } rs.close(); } stmt.close(); } if (queriedcolcount != 0) { // not all queried columns have resulted in a returned data row. // make sure we do not match those columns again next run for (int col = column; col < array_size; col++) { if (_is_fetched[col] != true && _is_queried[col]) { _is_fetched[col] = true; // for debug: System.out.println("Found NO match at [" + col + "] for " + getSchemaName(col) + "." + getTableName(col) + "." + getColumnName(col)); } } } } /** * Returns the number of columns in this ResultSet object. * * @return the number of columns */ @Override public int getColumnCount() { return columns.length; } /** * Indicates whether the designated column is automatically numbered. * * This method is currently very expensive for BIGINT, * INTEGER, SMALLINT and TINYINT result column types * as it needs to retrieve the information from the * database using an SQL meta data query. * * @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(final int column) throws SQLException { // only few integer types can be auto incrementable in MonetDB // see: https://www.monetdb.org/Documentation/SQLReference/DataTypes/SerialDatatypes switch (getColumnType(column)) { case Types.BIGINT: case Types.INTEGER: case Types.SMALLINT: case Types.TINYINT: try { if (_is_fetched[column] != true) { fetchColumnInfo(column); } return _isAutoincrement[column]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.newSQLInvalidColumnIndexException(column); } } return false; } /** * Indicates whether a column's case matters. * * @param column the first column is 1, the second is 2, ... * @return true for all character string columns else false */ @Override public boolean isCaseSensitive(final int column) throws SQLException { switch (getColumnType(column)) { case Types.CHAR: case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness case Types.CLOB: return true; case Types.VARCHAR: final String monettype = getColumnTypeName(column); if (monettype != null && monettype.length() == 4) { // data of type inet or uuid is not case sensitive if ("inet".equals(monettype) || "uuid".equals(monettype)) return false; } 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, ... * @return true */ @Override public boolean isSearchable(final int column) throws SQLException { checkColumnIndexValidity(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, ... * @return false */ @Override public boolean isCurrency(final int column) throws SQLException { checkColumnIndexValidity(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(final int column) throws SQLException { // we can hardcode this, based on the colum type switch (getColumnType(column)) { case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.REAL: case Types.FLOAT: case Types.DOUBLE: case Types.DECIMAL: case Types.NUMERIC: return true; case Types.BIGINT: final String monettype = getColumnTypeName(column); if (monettype != null && monettype.length() == 3) { // data of type oid or ptr is not signed if ("oid".equals(monettype) || "ptr".equals(monettype)) return false; } return true; // All other types should return false // case Types.BOOLEAN: // case Types.DATE: // can year be negative? // case Types.TIME: // can time be negative? // case Types.TIME_WITH_TIMEZONE: // case Types.TIMESTAMP: // can year be negative? // case Types.TIMESTAMP_WITH_TIMEZONE: 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(final int column) throws SQLException { checkColumnIndexValidity(column); if (header != null) { try { return header.getColumnLengths()[column - 1]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.newSQLInvalidColumnIndexException(column); } } return 1; } /** * 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(final int column) throws SQLException { checkColumnIndexValidity(column); if (schemas != null) { try { return schemas[column - 1]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.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(final int column) throws SQLException { checkColumnIndexValidity(column); if (tables != null) { try { return tables[column - 1]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.newSQLInvalidColumnIndexException(column); } } return ""; } /** * Get the designated column's specified column size. * For numeric data, this is the maximum precision. * For character data, this is the length in characters. * For datetime datatypes, this is the length in characters * of the String representation (assuming the maximum * allowed precision of the fractional seconds component). * For binary data, this is the length in bytes. * For the ROWID datatype, this is the length in bytes. * 0 is returned for data types where the column size is not applicable. * * This method is currently very expensive for DECIMAL, NUMERIC * CHAR, VARCHAR, CLOB, BLOB, VARBINARY and BINARY result * column types as it needs to retrieve the information * from the database using an SQL meta data 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(final int column) throws SQLException { final int tpe = getColumnType(column); switch (tpe) { case Types.BIGINT: return 19; case Types.INTEGER: return 10; case Types.SMALLINT: return 5; case Types.TINYINT: return 3; case Types.REAL: return 7; case Types.FLOAT: case Types.DOUBLE: return 15; case Types.DECIMAL: case Types.NUMERIC: // these data types do not have a fixed precision, max precision however is 38 // we need to fetch the defined precision with an SQL query ! 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.BINARY: case Types.VARBINARY: case Types.BLOB: // these data types also do not have a fixed length try { if (_is_fetched[column] != true) { fetchColumnInfo(column); } if (_precision[column] == 0) { // apparently no precision or max length could be fetched // use columnDisplaySize() value as alternative _precision[column] = getColumnDisplaySize(column); if (tpe == Types.BLOB || tpe == Types.VARBINARY || tpe == Types.BINARY) // These expect number of bytes, not number of hex chars _precision[column] = (_precision[column] / 2) +1; } return _precision[column]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.newSQLInvalidColumnIndexException(column); } case Types.DATE: return 10; // 2020-10-08 case Types.TIME: return 15; // 21:51:34.399753 case Types.TIME_WITH_TIMEZONE: return 21; // 21:51:34.399753+02:00 case Types.TIMESTAMP: return 26; // 2020-10-08 21:51:34.399753 case Types.TIMESTAMP_WITH_TIMEZONE: return 32; // 2020-10-08 21:51:34.399753+02:00 case Types.BOOLEAN: return 1; default: // All other types should return 0 return 0; } } /** * Gets the designated column's number of digits to right of * the decimal point. * 0 is returned for data types where the scale is not applicable. * * This method is currently very expensive for DECIMAL and NUMERIC * result column types as it needs to retrieve the information * from the database using an SQL meta data 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(final int column) throws SQLException { switch (getColumnType(column)) { case Types.DECIMAL: case Types.NUMERIC: { // special handling for: day_interval and sec_interval as these are mapped to these result types (see MonetDriver typemap) // they appear to have a fixed scale (tested against Oct2020) final String monettype = getColumnTypeName(column); if ("day_interval".equals(monettype)) return 0; if ("sec_interval".equals(monettype)) return 3; // these data types may have a variable scale, max scale is 38 try { if (_is_fetched[column] != true) { fetchColumnInfo(column); } return _scale[column]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.newSQLInvalidColumnIndexException(column); } } case Types.TIME: case Types.TIME_WITH_TIMEZONE: case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: // support microseconds, so scale 6 return 6; // 21:51:34.399753 // All other types should return 0 // case Types.BIGINT: // case Types.INTEGER: // case Types.SMALLINT: // case Types.TINYINT: // case Types.REAL: // case Types.FLOAT: // case Types.DOUBLE: // 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.BINARY: // case Types.VARBINARY: // case Types.BLOB: // case Types.DATE: // case Types.BOOLEAN: default: return 0; } } /** * 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 * meta data query (for each column). * * @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(final int column) throws SQLException { checkColumnIndexValidity(column); try { if (_is_fetched[column] != true) { fetchColumnInfo(column); } return _isNullable[column]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.newSQLInvalidColumnIndexException(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(final int column) throws SQLException { checkColumnIndexValidity(column); 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(final int column) throws SQLException { checkColumnIndexValidity(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(final int column) throws SQLException { checkColumnIndexValidity(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(final int column) throws SQLException { checkColumnIndexValidity(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(final int column) throws SQLException { checkColumnIndexValidity(column); try { final String MonetDBType = types[column - 1]; Class<?> type = null; if (conn != null) { final Map<String,Class<?>> map = conn.getTypeMap(); if (map != null && map.containsKey(MonetDBType)) { type = (Class)map.get(MonetDBType); } } 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: " + MonetDBType, "M0M03"); } catch (IndexOutOfBoundsException e) { throw MonetResultSet.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(final 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(final int column) throws SQLException { checkColumnIndexValidity(column); try { return columns[column - 1]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.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(final int column) throws SQLException { checkColumnIndexValidity(column); try { return JdbcSQLTypes[column - 1]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.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(final int column) throws SQLException { checkColumnIndexValidity(column); try { return types[column - 1]; } catch (IndexOutOfBoundsException e) { throw MonetResultSet.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 or this method is called on a closed result set */ @Override public Object getObject(final 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 checkNotClosed(); final int JdbcType; final String val; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; JdbcType = JdbcSQLTypes[columnIndex - 1]; } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } switch(JdbcType) { 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. final String MonetDBType = types[columnIndex - 1]; switch (MonetDBType.length()) { case 3: if ("url".equals(MonetDBType)) { try { final org.monetdb.jdbc.types.URL url_obj = new org.monetdb.jdbc.types.URL(); url_obj.fromString(val); return url_obj; } catch (Exception exc) { // ignore exception and just return the val String object return val; } } break; case 4: if ("inet".equals(MonetDBType)) { try { final org.monetdb.jdbc.types.INET inet_obj = new org.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 ("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 ("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; } break; } 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 new MonetBlob(val); case Types.DATE: return getDate(columnIndex, null); case Types.TIME: case Types.TIME_WITH_TIMEZONE: return getTime(columnIndex, null); case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: return getTimestamp(columnIndex, null); 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 final boolean classImplementsSQLData(final Class<?> cl) { final 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 columnIndex 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 or this method is called on a closed result set */ @Override @SuppressWarnings("unchecked") public Object getObject(final int columnIndex, final Map<String,Class<?>> map) throws SQLException { checkNotClosed(); 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 SQL type 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, null); } else if (type == Time.class) { return getTime(columnIndex, null); } else if (type == Timestamp.class) { return getTimestamp(columnIndex, null); } else if (type == Clob.class) { return getClob(columnIndex); } else if (type == Blob.class) { return getBlob(columnIndex); } else if (classImplementsSQLData(type)) { final SQLData x; try { final java.lang.reflect.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 (java.lang.reflect.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(); final 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, null); } @Override public java.sql.Time readTime() throws SQLException { return getTime(colnum, null); } @Override public Timestamp readTimestamp() throws SQLException { return getTimestamp(colnum, null); } @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; } } /** * 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 */ final static Class<?> getClassForType(final 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.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: case Types.TIME_WITH_TIMEZONE: return Time.class; case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: 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 columnLabel the SQL name of the column * @return a java.lang.Object holding the column value * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @Override public Object getObject(final String columnLabel) throws SQLException { return getObject(findColumn(columnLabel)); } /** * 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 columnLabel 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 or this method is called on a closed result set */ @Override public Object getObject(final String columnLabel, final Map<String,Class<?>> map) throws SQLException { return getObject(findColumn(columnLabel), map); } @Override public Ref getRef(int columnIndex) throws SQLException { throw newSQLFeatureNotSupportedException("getRef"); } @Override public Ref getRef(final String columnLabel) 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(final 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 columnLabel 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 columnLabel * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override public RowId getRowId(final String columnLabel) 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 or this method is called on a closed result set */ @Override public short getShort(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return 0; } lastReadWasNull = false; return Short.parseShort(val); } catch (NumberFormatException e) { throw newSQLNumberFormatException(e); } 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 columnLabel 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 columnLabel */ @Override public short getShort(final String columnLabel) throws SQLException { return getShort(findColumn(columnLabel)); } /** * 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 may return null. * * In our implementation we always return a non-null object, see constructors. * Also from subclass MonetVirtualResultSet, see its constructor. * * @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 or this method is called on a closed result set */ @Override public String getString(final int columnIndex) throws SQLException { checkNotClosed(); try { final 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 columnLabel 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 columnLabel */ @Override public String getString(final String columnLabel) throws SQLException { return getString(findColumn(columnLabel)); } /** * 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 */ @Override public String getNString(final 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 columnLabel 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 columnLabel */ @Override public String getNString(final String columnLabel) throws SQLException { return getString(findColumn(columnLabel)); } /** * 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 columnIndex 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(final int columnIndex) 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 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 * @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(final String columnLabel) throws SQLException { throw newSQLFeatureNotSupportedException("getSQLXML"); } // This behaviour is according table B-6 of Sun JDBC Specification 3.0 private SimpleDateFormat dateFormat; private SimpleDateFormat timeFormat; private SimpleDateFormat timestampFormat; /** * 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 columnIndex 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 final int getJavaDate(final Calendar cal, final int columnIndex, int type) throws SQLException { checkNotClosed(); if (cal == null) throw new IllegalArgumentException("No Calendar object given!"); final String monetDateStr; final String monetDate; final String MonetDBType; int JdbcType; boolean negativeYear = false; try { monetDateStr = tlp.values[columnIndex - 1]; if (monetDateStr == 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; } if ((JdbcType == Types.DATE || JdbcType == Types.TIMESTAMP || JdbcType == Types.TIMESTAMP_WITH_TIMEZONE) && monetDateStr.startsWith("-")) { // the SimpleDateFormat parsers do not support to parse negative year numbers, deal with it separately negativeYear = true; monetDate = monetDateStr.substring(1); } else { monetDate = monetDateStr; } } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } 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 (MonetDBType != null && ("timetz".equals(MonetDBType) || "timestamptz".equals(MonetDBType))) { int vallen = monetDate.length(); if (vallen >= 6) { // MonetDB/SQL99: Sign TwoDigitHours : Minutes ptz = TimeZone.getTimeZone("GMT" + monetDate.substring(vallen - 6, vallen)); } } java.util.Date pdate = null; final java.text.ParsePosition ppos = new java.text.ParsePosition(0); switch(JdbcType) { case Types.DATE: if (dateFormat == null) { // first time usage, create and keep the dateFormat object for next usage dateFormat = new SimpleDateFormat("yyyy-MM-dd"); } dateFormat.setTimeZone(ptz); pdate = dateFormat.parse(monetDate, ppos); break; case Types.TIME: case Types.TIME_WITH_TIMEZONE: if (timeFormat == null) { // first time usage, create and keep the timeFormat object for next usage timeFormat = new SimpleDateFormat("HH:mm:ss"); } timeFormat.setTimeZone(ptz); pdate = timeFormat.parse(monetDate, ppos); break; case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: if (timestampFormat == null) { // first time usage, create and keep the timestampFormat object for next usage timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } timestampFormat.setTimeZone(ptz); pdate = timestampFormat.parse(monetDate, ppos); // if parsing with timestampFormat failed try to parse it in dateFormat if (pdate == null && monetDate.length() <= 10 && monetDate.contains("-")) { if (dateFormat == null) { // first time usage, create and keep the dateFormat object for next usage dateFormat = new SimpleDateFormat("yyyy-MM-dd"); } dateFormat.setTimeZone(ptz); pdate = dateFormat.parse(monetDate, ppos); } break; default: throw new SQLException("Internal error, unsupported data type: " + type, "01M03"); } if (pdate == null) { // parsing failed final StringBuilder errMsg = new StringBuilder(128); final int epos = ppos.getErrorIndex(); if (epos == -1) { errMsg.append("parsing '").append(monetDateStr).append("' failed"); } else if (epos < monetDate.length()) { errMsg.append("parsing failed at pos ").append(epos + (negativeYear ? 2 : 1)) .append(" found: '").append(monetDate.charAt(epos)) .append("' in '").append(monetDateStr).append("'"); } else { errMsg.append("parsing failed, expected more data after '").append(monetDateStr).append("'"); } throw new SQLException(errMsg.toString(), "01M10"); } cal.setTime(pdate); if (negativeYear) { // System.out.println("Current cal: " + cal.toString()); // using cal.set(Calendar.YEAR, -(cal.get(Calendar.YEAR))); does not work. We must set the ERA instead cal.set(Calendar.ERA, java.util.GregorianCalendar.BC); // System.out.println("Corrected cal: " + cal.toString()); } if (JdbcType == Types.TIME || JdbcType == Types.TIME_WITH_TIMEZONE || JdbcType == Types.TIMESTAMP || JdbcType == Types.TIMESTAMP_WITH_TIMEZONE) { // parse additional nanos (if any) int nanos = 0; int pos = ppos.getIndex(); final char[] monDate = monetDate.toCharArray(); if (pos < monDate.length && monDate[pos] == '.') { pos++; try { int ctr; 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) { final int offset = e.getErrorOffset(); addWarning(e.getMessage() + " found: '" + monDate[offset] + "' in: \"" + monetDate + "\" at pos: " + offset, "01M10"); // default value nanos = 0; } } return nanos; } return 0; } /** * 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 static final int getIntrinsicValue(final char c, final 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 as a java.sql.Date object; 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(final int columnIndex) throws SQLException { return getDate(columnIndex, null); } /** * 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 as a java.sql.Date object; 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(final int columnIndex, Calendar cal) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; if (cal == null) { // try to convert string directly to a Date object // Note: the string must be in JDBC date escape format: yyyy-[m]m-[d]d try { return java.sql.Date.valueOf(val); } catch (IllegalArgumentException iae) { // this happens if string doesn't match the format, such as for years < 1000 (including negative years) // in those cases just continue and use slower getJavaDate(cal, columnIndex, Types.DATE) method } cal = Calendar.getInstance(); } if (getJavaDate(cal, columnIndex, Types.DATE) == -1) return null; return new java.sql.Date(cal.getTimeInMillis()); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } } /** * 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 columnLabel the SQL name of the column from which to retrieve the value * @return the column value as a java.sql.Date object; 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(final String columnLabel) throws SQLException { return getDate(findColumn(columnLabel), null); } /** * 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 columnLabel 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 as a java.sql.Date object; 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(final String columnLabel, final Calendar cal) throws SQLException { return getDate(findColumn(columnLabel), 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 as a java.sql.Time object; if the value is SQL NULL, the value returned is null * @throws SQLException if a database access error occurs */ @Override public Time getTime(final int columnIndex) throws SQLException { return getTime(columnIndex, null); } /** * 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.Time object; if the value is SQL NULL, the value returned is null * @throws SQLException if a database access error occurs */ @Override public Time getTime(final int columnIndex, Calendar cal) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; if (cal == null) { // try to convert string directly to a Time object // Note: the string must be in JDBC time escape format: hh:mm:ss try { return Time.valueOf(val); } catch (IllegalArgumentException iae) { // this happens if string doesn't match the format or hh >= 24 or mm >= 60 or ss >= 60 // in those cases just continue and use slower getJavaDate(cal, columnIndex, Types.TIME) method } cal = Calendar.getInstance(); } if (getJavaDate(cal, columnIndex, Types.TIME) == -1) return null; return new Time(cal.getTimeInMillis()); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } } /** * 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 columnLabel the SQL name of the column * @return the column value as a java.sql.Time object; if the value is SQL NULL, the value returned is null * @throws SQLException if a database access error occurs */ @Override public Time getTime(final String columnLabel) throws SQLException { return getTime(findColumn(columnLabel), null); } /** * 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 columnLabel 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.Time object; if the value is SQL NULL, the value returned is null * @throws SQLException if a database access error occurs */ @Override public Time getTime(final String columnLabel, final Calendar cal) throws SQLException { return getTime(findColumn(columnLabel), 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 as a java.sql.Timestamp object; if the value is SQL NULL, the value returned is null * @throws SQLException if a database access error occurs */ @Override public Timestamp getTimestamp(final int columnIndex) throws SQLException { return getTimestamp(columnIndex, null); } /** * 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 * @throws SQLException if a database access error occurs */ @Override public Timestamp getTimestamp(final int columnIndex, Calendar cal) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; if (cal == null) { // try to convert the string directly to a Timestamp object // Note: the string must be in JDBC timestamp escape format: yyyy-[m]m-[d]d hh:mm:ss[.f...] try { return Timestamp.valueOf(val); } catch (IllegalArgumentException iae) { // this happens if string doesn't match the format, such as for years < 1000 (including negative years) // in those cases just continue and use slower getJavaDate(cal, columnIndex, Types.TIMESTAMP) method } cal = Calendar.getInstance(); } final int nanos = getJavaDate(cal, columnIndex, Types.TIMESTAMP); if (nanos == -1) return null; final Timestamp ts = new Timestamp(cal.getTimeInMillis()); ts.setNanos(nanos); return ts; } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } } /** * 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 columnLabel the SQL name of the column * @return the column value as a java.sql.Timestamp object; if the value is SQL NULL, the value returned is null * @throws SQLException if a database access error occurs */ @Override public Timestamp getTimestamp(final String columnLabel) throws SQLException { return getTimestamp(findColumn(columnLabel), null); } /** * 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 columnLabel 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 * @throws SQLException if a database access error occurs */ @Override public Timestamp getTimestamp(final String columnLabel, final Calendar cal) throws SQLException { return getTimestamp(findColumn(columnLabel), 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 * @throws SQLException if a database access error occurs, or if a URL is malformed */ @Override public URL getURL(final int columnIndex) throws SQLException { checkNotClosed(); try { final String val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; try { return new URL(val); } catch (java.net.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 columnLabel 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 * @throws SQLException if a database access error occurs, or if a URL is malformed */ @Override public URL getURL(final String columnLabel) throws SQLException { return getURL(findColumn(columnLabel)); } /** * 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 result set */ @Override public SQLWarning getWarnings() throws SQLException { checkNotClosed(); // 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 * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @Override public boolean isAfterLast() throws SQLException { checkNotClosed(); 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 * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @Override public boolean isBeforeFirst() throws SQLException { checkNotClosed(); 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 * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @Override public boolean isClosed() throws SQLException { 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 * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @Override public boolean isFirst() throws SQLException { checkNotClosed(); 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 * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @Override public boolean isLast() throws SQLException { checkNotClosed(); 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 * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @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(final 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 * Since: 1.2 * See Also: DatabaseMetaData.deletesAreDetected(int) */ @Override public boolean rowDeleted() throws SQLException { checkNotClosed(); 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 * Since: 1.2 * See Also: DatabaseMetaData.insertsAreDetected(int) */ @Override public boolean rowInserted() throws SQLException { checkNotClosed(); 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 * Since: 1.2 * See Also: DatabaseMetaData.updatesAreDetected(int) */ @Override public boolean rowUpdated() throws SQLException { checkNotClosed(); return false; } /* Next methods are all related to updateable result sets, which we do not support. * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method */ @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 columnLabel, Array x) throws SQLException { throw newSQLFeatureNotSupportedException("updateArray"); } @Override public void updateAsciiStream(int columnIndex, InputStream x) 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 columnLabel, InputStream x) throws SQLException { throw newSQLFeatureNotSupportedException("updateAsciiStream"); } @Override public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { throw newSQLFeatureNotSupportedException("updateAsciiStream"); } @Override public void updateAsciiStream(String columnLabel, 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 columnLabel, 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 columnLabel, InputStream x) throws SQLException { throw newSQLFeatureNotSupportedException("updateBinaryStream"); } @Override public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { throw newSQLFeatureNotSupportedException("updateBinaryStream"); } @Override public void updateBinaryStream(String columnLabel, 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 columnLabel, Blob x) throws SQLException { throw newSQLFeatureNotSupportedException("updateBlob"); } @Override public void updateBlob(String columnLabel, InputStream s) throws SQLException { throw newSQLFeatureNotSupportedException("updateBlob"); } @Override public void updateBlob(String columnLabel, 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 columnLabel, 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 columnLabel, 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 columnLabel, 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 columnLabel, Reader reader) throws SQLException { throw newSQLFeatureNotSupportedException("updateCharacterStream"); } @Override public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { throw newSQLFeatureNotSupportedException("updateCharacterStream"); } @Override public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { throw newSQLFeatureNotSupportedException("updateCharacterStream"); } @Override public void updateNCharacterStream(int columnIndex, Reader x) 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 columnLabel, Reader reader) throws SQLException { throw newSQLFeatureNotSupportedException("updateNCharacterStream"); } @Override public void updateNCharacterStream(String columnLabel, 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 columnLabel, Clob x) throws SQLException { throw newSQLFeatureNotSupportedException("updateClob"); } @Override public void updateClob(String columnLabel, Reader r) throws SQLException { throw newSQLFeatureNotSupportedException("updateClob"); } @Override public void updateClob(String columnLabel, 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 columnLabel, NClob x) throws SQLException { throw newSQLFeatureNotSupportedException("updateNClob"); } @Override public void updateNClob(String columnLabel, Reader r) throws SQLException { throw newSQLFeatureNotSupportedException("updateNClob"); } @Override public void updateNClob(String columnLabel, 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 columnLabel, 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 columnLabel, 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 columnLabel, 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 columnLabel, 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 columnLabel, long x) throws SQLException { throw newSQLFeatureNotSupportedException("updateLong"); } @Override public void updateNull(int columnIndex) throws SQLException { throw newSQLFeatureNotSupportedException("updateNull"); } @Override public void updateNull(String columnLabel) 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 columnLabel, Object x) throws SQLException { throw newSQLFeatureNotSupportedException("updateObject"); } @Override public void updateObject(String columnLabel, 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 columnLabel, 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 columnLabel, 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 columnLabel, 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 columnLabel, String x) throws SQLException { throw newSQLFeatureNotSupportedException("updateNString"); } @Override public void updateSQLXML(int columnIndex, SQLXML x) throws SQLException { throw newSQLFeatureNotSupportedException("updateSQLXML"); } @Override public void updateSQLXML(String columnLabel, 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 columnLabel, 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 columnLabel, 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; } //== Java 1.7 methods (JDBC 4.1) /** * 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 columnIndex 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 * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override public <T> T getObject(final int columnIndex, final Class<T> type) throws SQLException { checkNotClosed(); if (type == null) throw new SQLException("type is null", "M1M05"); throw newSQLFeatureNotSupportedException("getObject(column, Class<T> type)"); } /** * 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 * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override public <T> T getObject(final String columnLabel, final Class<T> type) throws SQLException { return getObject(findColumn(columnLabel), type); } //== Java 1.8 methods (JDBC 4.2) @Override public void updateObject(int columnIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { throw newSQLFeatureNotSupportedException("updateObject"); } @Override public void updateObject(String columnLabel, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { throw newSQLFeatureNotSupportedException("updateObject"); } @Override public void updateObject(int columnIndex, Object x, SQLType targetSqlType) throws SQLException { throw newSQLFeatureNotSupportedException("updateObject"); } @Override public void updateObject(String columnLabel, Object x, SQLType targetSqlType) throws SQLException { throw newSQLFeatureNotSupportedException("updateObject"); } //== end methods of interface ResultSet //== internal helper methods which do not belong to the JDBC interface /** * 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(final String reason, final String sqlstate) { SQLWarning warng = new SQLWarning(reason, sqlstate); if (warnings == null) { warnings = warng; } else { warnings.setNextWarning(warng); } } /** * Local helper method to test whether the ResultSet object is closed * When closed it throws an SQLException */ private void checkNotClosed() throws SQLException { if (isClosed()) throw new SQLException("ResultSet is closed", "M1M20"); } /** * Small helper method that formats the "Invalid Column Index number ..." message * and creates a new SQLDataException object whose SQLState is set * to "22010": invalid indicator parameter value. * * @param colIdx the column index number * @return a new created SQLDataException object with SQLState 22010 */ public static final SQLDataException newSQLInvalidColumnIndexException(final int colIdx) { return new SQLDataException("Invalid Column Index number: " + colIdx, "22010"); } /** * Small helper method that formats the "Could not convert value to a number" message * and creates a new SQLDataException object whose SQLState is set * to "22003": Numeric value out of range. * * @param error the NumberFormatException * @return a new created SQLDataException object with SQLState 22003 */ private static final SQLDataException newSQLNumberFormatException(final NumberFormatException error) { return new SQLDataException("Could not convert value to a number. " + error.getMessage(), "22003"); } }