Mercurial > hg > monetdb-java
view src/main/java/org/monetdb/jdbc/MonetResultSet.java @ 970:f90d811e97eb default tip
Adjust getTableTypes() test for new table type: LOCAL TEMPORARY VIEW, added in 11.53.4 (Mar2025-SP1)
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Thu, 03 Apr 2025 15:01:33 +0200 (4 days ago) |
parents | ff075ed5ce81 |
children |
line wrap: on
line source
/* * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2024, 2025 MonetDB Foundation; * Copyright August 2008 - 2023 MonetDB B.V.; * Copyright 1997 - July 2008 CWI. */ 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.Date; 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.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.format.DateTimeFormatter; 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 once from the MonetDB String[] types */ private final int[] JdbcSQLTypes; /** A cache to reduce the number of ResultSetMetaData objects created by getMetaData() to maximum 1 per ResultSet */ private ResultSetMetaData rsmd; // 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() { clearWarnings(); if (header != null && !header.isClosed()) { header.close(); } rsmd = null; 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: // MonetDB doesn't use type 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 */ 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: // MonetDB doesn't use type 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 successful 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 successful 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)); } /** * Retrieves the number, types and properties of this ResultSet object's * columns. * * @return the description of this ResultSet object's columns * @throws SQLException if a database access error occurs or this method is called on a closed result set */ @Override public ResultSetMetaData getMetaData() throws SQLException { if (rsmd == null) { // first use, construct it once and reuse it for all next calls if (header != null) { rsmd = new MonetResultSetMetaData((MonetConnection) getStatement().getConnection(), header); } else { // this will be a MonetVirtualResultSet object, see MonetStatement.getGeneratedKeys() // create arrays for storing the result columns meta data to pass to the constructor final int array_size = 1; final String[] schemas = new String[array_size]; final String[] tables = new String[array_size]; final String[] columns = new String[array_size]; final String[] types = new String[array_size]; final int[] jdbcTypes = new int[array_size]; final int[] lengths = new int[array_size]; final int[] precisions = new int[array_size]; final int[] scales = new int[array_size]; // fill the arrays with the getGeneratedKeys() resultset columns metadata. It only has 1 column. schemas[0] = null; tables[0] = null; columns[0] = "GENERATED_KEY"; types[0] = "bigint"; jdbcTypes[0] = Types.BIGINT; lengths[0] = 20; precisions[0] = 19; scales[0] = 0; rsmd = new MonetResultSetMetaData((MonetConnection) getStatement().getConnection(), 1, schemas, tables, columns, types, jdbcTypes, lengths, precisions, scales); } } return rsmd; } /** * 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, uuid and xml are all mapped to Types.VARCHAR in MonetDriver.typeMap // For these MonetDB types (except json and xml, 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 */ 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: return getTime(columnIndex, null); case Types.TIME_WITH_TIMEZONE: return getOffsetTime(columnIndex); case Types.TIMESTAMP: return getTimestamp(columnIndex, null); case Types.TIMESTAMP_WITH_TIMEZONE: return getOffsetDateTime(columnIndex); case Types.BINARY: case Types.VARBINARY: /* case Types.LONGVARBINARY: // MonetDB doesn't use type LONGVARBINARY */ 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 = MonetDriver.getClassForType(JdbcSQLTypes[columnIndex - 1]); } if (type == null || type == String.class) { return val; } if (type == BigDecimal.class) { return getBigDecimal(columnIndex); } if (type == Boolean.class) { return Boolean.valueOf(getBoolean(columnIndex)); } if (type == Integer.class) { return Integer.valueOf(getInt(columnIndex)); } if (type == Long.class) { return Long.valueOf(getLong(columnIndex)); } if (type == Short.class) { return Short.valueOf(getShort(columnIndex)); } if (type == Double.class) { return Double.valueOf(getDouble(columnIndex)); } if (type == Float.class) { return Float.valueOf(getFloat(columnIndex)); } if (type == Date.class) { return getDate(columnIndex, null); } if (type == Time.class) { return getTime(columnIndex, null); } if (type == Timestamp.class) { return getTimestamp(columnIndex, null); } if (type == LocalDate.class) { return getLocalDate(columnIndex); } if (type == LocalDateTime.class) { return getLocalDateTime(columnIndex); } if (type == LocalTime.class) { return getLocalTime(columnIndex); } if (type == OffsetDateTime.class) { return getOffsetDateTime(columnIndex); } if (type == OffsetTime.class) { return getOffsetTime(columnIndex); } if (type == Clob.class) { return getClob(columnIndex); } if (type == Blob.class) { return getBlob(columnIndex); } if (type == byte[].class) { return getBytes(columnIndex); } 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 Date readDate() throws SQLException { return getDate(colnum, null); } @Override public 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; } return val; } /** * 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 Calendar 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 || // MonetDB doesn't use type 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 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 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 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 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 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 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 { // Note: as of Java version 20 java.net.URL(String) constructor is deprecated. // https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/net/URL.html#%3Cinit%3E(java.lang.String) return new java.net.URI(val).toURL(); } catch (java.net.URISyntaxException | java.net.MalformedURLException e) { throw new SQLException(e.getMessage(), "22M30"); } } 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, Date x) throws SQLException { throw newSQLFeatureNotSupportedException("updateDate"); } @Override public void updateDate(String columnLabel, 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 */ @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"); final String val; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } if (type == String.class) { return type.cast(val); } if (type == Integer.class) { return type.cast(Integer.valueOf(getInt(columnIndex))); } if (type == Long.class) { return type.cast(Long.valueOf(getLong(columnIndex))); } if (type == Short.class) { return type.cast(Short.valueOf(getShort(columnIndex))); } if (type == Double.class) { return type.cast(Double.valueOf(getDouble(columnIndex))); } if (type == Float.class) { return type.cast(Float.valueOf(getFloat(columnIndex))); } if (type == BigDecimal.class) { return type.cast(getBigDecimal(columnIndex)); } if (type == Boolean.class) { return type.cast(Boolean.valueOf(getBoolean(columnIndex))); } if (type == byte[].class) { return type.cast(getBytes(columnIndex)); } if (type == Blob.class || type == MonetBlob.class) { return type.cast(getBlob(columnIndex)); } if (type == Clob.class || type == MonetClob.class) { return type.cast(getClob(columnIndex)); } if (type == URL.class) { return type.cast(getURL(columnIndex)); } if (type == Date.class) { return type.cast(getDate(columnIndex, null)); } if (type == Time.class) { return type.cast(getTime(columnIndex, null)); } if (type == Timestamp.class) { return type.cast(getTimestamp(columnIndex, null)); } if (type == LocalDate.class) { return type.cast(getLocalDate(columnIndex)); } if (type == LocalDateTime.class) { return type.cast(getLocalDateTime(columnIndex)); } if (type == LocalTime.class) { return type.cast(getLocalTime(columnIndex)); } if (type == OffsetDateTime.class) { return type.cast(getOffsetDateTime(columnIndex)); } if (type == OffsetTime.class) { return type.cast(getOffsetTime(columnIndex)); } if (type == java.util.Date.class) { final Timestamp timestamp = getTimestamp(columnIndex, null); return type.cast(new java.util.Date(timestamp.getTime())); } if (type == Calendar.class) { final Calendar cal = Calendar.getInstance(); getJavaDate(cal, columnIndex, Types.TIMESTAMP); return type.cast(cal); } if (type == java.util.UUID.class) { try { return type.cast(java.util.UUID.fromString(val)); } catch (IllegalArgumentException exc) { throw new SQLException("conversion to java.util.UUID object failed: " + exc.getMessage(), "M1M05"); } } if (type == java.net.InetAddress.class) { final int slash = val.indexOf('/'); try { return type.cast(java.net.InetAddress.getByName(slash < 0 ? val : val.substring(0, slash))); } catch (java.net.UnknownHostException exc) { throw new SQLException("conversion to java.net.InetAddress object failed: " + exc.getMessage(), "M1M05"); } } throw new SQLException("conversion to '" + type.getName() + "' is not supported", "M1M05"); } /** * Retrieves the value of the designated column in the current row * of this ResultSet object and will convert from the SQL type of * the column to the requested Java data type, if the conversion is * supported. If the conversion is not supported or null is * specified for the type, a SQLException is thrown. * * @param columnLabel the label for the column specified with the * SQL AS clause. If the SQL AS clause was not specified, * then the label is the name of the column * @param type Class representing the Java data type to convert the * designated column to * @return an instance of type holding the column value * @throws SQLException if conversion is not supported, type is * null or another error occurs. The getCause() method of * the exception may provide a more detailed exception, for * example, if a conversion error occurs */ @Override public <T> T getObject(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 * @param sqlstate the SQLState code (5 characters) */ 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 * * @throws SQLException if this ResultSet is closed */ private void checkNotClosed() throws SQLException { if (isClosed()) throw new SQLException("ResultSet is closed", "M1M20"); } /** * Retrieves the value of the designated column in the current row * of this ResultSet object and will convert to LocalDate. * If the conversion is not supported a SQLException is thrown. * * @param columnIndex the first column is 1, the second is 2, ... * @return LocalDate object or null * @throws SQLException if conversion is not supported */ private LocalDate getLocalDate(final int columnIndex) throws SQLException { final String val; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; // Note: ISO_LOCAL_DATE format requires the year to have 4 (or more) digits else parse will fail // This means years -999 to 999 will fail to parse. They should have been zero padded, so -0999 to 0999. return LocalDate.parse(val, DateTimeFormatter.ISO_LOCAL_DATE); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } catch (java.time.format.DateTimeParseException e) { throw new SQLException("Failed to convert to LocalDate: " + e.getMessage(), "22M33"); } } /** * Retrieves the value of the designated column in the current row * of this ResultSet object and will convert to LocalDateTime. * If the conversion is not supported a SQLException is thrown. * * @param columnIndex the first column is 1, the second is 2, ... * @return LocalDateTime object or null * @throws SQLException if conversion is not supported */ private LocalDateTime getLocalDateTime(final int columnIndex) throws SQLException { final String val; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; // ISO_LOCAL_DATE_TIME format expects a 'T' instead of a space between date and time parts // replace the space between date and time parts with 'T' String val_new = val; final int space = val.indexOf(' ', 4); if (space > 4 && space < 16) { val_new = val.substring(0, space) + "T" + val.substring(space + 1); // System.out.println("getLocalDateTime() changed " + val + " into " + val_new); } // Note: ISO_LOCAL_DATE_TIME format requires the year to have 4 (or more) digits else parse will fail // This means years -999 to 999 will fail to parse. They should have been zero padded, so -0999 to 0999. return LocalDateTime.parse(val_new, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } catch (java.time.format.DateTimeParseException e) { throw new SQLException("Failed to convert to LocalDateTime: " + e.getMessage(), "22M35"); } } /** * Retrieves the value of the designated column in the current row * of this ResultSet object and will convert to LocalTime. * If the conversion is not supported a SQLException is thrown. * * @param columnIndex the first column is 1, the second is 2, ... * @return LocalTime object or null * @throws SQLException if conversion is not supported */ private LocalTime getLocalTime(final int columnIndex) throws SQLException { final String val; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; return LocalTime.parse(val, DateTimeFormatter.ISO_LOCAL_TIME); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } catch (java.time.format.DateTimeParseException e) { throw new SQLException("Failed to convert to LocalTime: " + e.getMessage(), "22M34"); } } /** * Retrieves the value of the designated column in the current row * of this ResultSet object and will convert to OffsetDateTime. * If the conversion is not supported a SQLException is thrown. * * @param columnIndex the first column is 1, the second is 2, ... * @return OffsetDateTime object or null * @throws SQLException if conversion is not supported */ private OffsetDateTime getOffsetDateTime(final int columnIndex) throws SQLException { final String val; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; // ISO_OFFSET_DATE_TIME format expects a 'T' instead of a space between date and time parts // replace the space between date and time parts with 'T' String val_new = val; final int space = val.indexOf(' ', 4); if (space > 4 && space < 16) { val_new = val.substring(0, space) + "T" + val.substring(space + 1); // System.out.println("getOffsetDateTime() changed " + val + " into " + val_new); } // Note: ISO_OFFSET_DATE_TIME format requires the year to have 4 (or more) digits else parse will fail // This means years -999 to 999 will fail to parse. They should have been zero padded, so -0999 to 0999. return OffsetDateTime.parse(val_new, DateTimeFormatter.ISO_OFFSET_DATE_TIME); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } catch (java.time.format.DateTimeParseException e) { throw new SQLException("Failed to convert to OffsetDateTime: " + e.getMessage(), "22M37"); } } /** * Retrieves the value of the designated column in the current row * of this ResultSet object and will convert to OffsetTime. * If the conversion is not supported a SQLException is thrown. * * @param columnIndex the first column is 1, the second is 2, ... * @return OffsetTime object or null * @throws SQLException if conversion is not supported */ private OffsetTime getOffsetTime(final int columnIndex) throws SQLException { final String val; try { val = tlp.values[columnIndex - 1]; if (val == null) { lastReadWasNull = true; return null; } lastReadWasNull = false; return OffsetTime.parse(val, DateTimeFormatter.ISO_TIME); } catch (IndexOutOfBoundsException e) { throw newSQLInvalidColumnIndexException(columnIndex); } catch (java.time.format.DateTimeParseException e) { throw new SQLException("Failed to convert to OffsetTime: " + e.getMessage(), "22M36"); } } /** * 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"); } }