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 &lt;= 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");
	}
}