diff src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java @ 0:a5a898f6886c

Copy of MonetDB java directory changeset e6e32756ad31.
author Sjoerd Mullender <sjoerd@acm.org>
date Wed, 21 Sep 2016 09:34:48 +0200 (2016-09-21)
parents
children 749e3cf8b2aa
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetStatement.java
@@ -0,0 +1,1286 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.  If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+ */
+
+package nl.cwi.monetdb.jdbc;
+
+import java.sql.*;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import nl.cwi.monetdb.mcl.net.*;
+
+/**
+ * A Statement suitable for the MonetDB database.
+ * 
+ * The object used for executing a static SQL statement and returning
+ * the results it produces.<br />
+ * 
+ * By default, only one {@link ResultSet} object per Statement object can be
+ * open at the same time. Therefore, if the reading of one ResultSet
+ * object is interleaved with the reading of another, each must have
+ * been generated by different {@link Statement} objects. All execution methods
+ * in the Statement interface implicitly close a Statement's current
+ * ResultSet object if an open one exists.
+ * 
+ * The current state of this Statement is that it only implements the
+ * executeQuery() which returns a ResultSet where from results can be
+ * read and executeUpdate() which doesn't return the affected rows.
+ * Commit and rollback are implemented, as is the autoCommit mechanism
+ * which relies on server side auto commit.<br />
+ * Multi-result queries are supported using the getMoreResults() method.
+ *
+ * @author Fabian Groffen
+ * @version 0.7
+ */
+public class MonetStatement extends MonetWrapper implements Statement {
+	/** the default value of maxRows, 0 indicates unlimited */
+	static final int DEF_MAXROWS = 0;
+
+	/** The parental Connection object */
+	private MonetConnection connection;
+	/** The last ResponseList object this Statement produced */
+	private MonetConnection.ResponseList lastResponseList;
+	/** The last Response that this object uses */
+	MonetConnection.Response header;
+	/** The warnings this Statement object generated */
+	private SQLWarning warnings;
+	/** Whether this Statement object is closed or not */
+	protected boolean closed;
+	/** Whether the application wants this Statement object to be pooled */
+	protected boolean poolable;
+	/** Whether this Statement should be closed if the last ResultSet
+	 * closes */
+	private boolean closeOnCompletion = false;
+	/** The size of the blocks of results to ask for at the server */
+	private int fetchSize = 0;
+	/** The maximum number of rows to return in a ResultSet */
+	private int maxRows = DEF_MAXROWS;
+	/** The suggested direction of fetching data (implemented but not used) */
+	private int fetchDirection = ResultSet.FETCH_FORWARD;
+	/** The type of ResultSet to produce; i.e. forward only, random access */
+	private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
+	/** The concurrency of the ResultSet to produce */
+	private int resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
+
+	/** A List to hold all queries of a batch */
+	private List<String> batch = new ArrayList<String>();
+
+
+	/**
+	 * MonetStatement constructor which checks the arguments for validity, tries
+	 * to set up a socket to MonetDB and attempts to login.
+	 * This constructor is only accessible to classes from the jdbc package.
+	 *
+	 * @param connection the connection that created this Statement
+	 * @param resultSetType type of ResultSet to produce
+	 * @param resultSetConcurrency concurrency of ResultSet to produce
+	 * @throws SQLException if an error occurs during login
+	 * @throws IllegalArgumentException is one of the arguments is null or empty
+	 */
+	MonetStatement(
+		MonetConnection connection,
+		int resultSetType,
+		int resultSetConcurrency,
+		int resultSetHoldability)
+		throws SQLException, IllegalArgumentException
+	{
+		if (connection == null) throw
+			new IllegalArgumentException("No Connection given!");
+
+		this.connection = connection;
+		this.resultSetType = resultSetType;
+		this.resultSetConcurrency = resultSetConcurrency;
+
+		// check our limits, and generate warnings as appropriate
+		if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) {
+			addWarning("No concurrency mode other then read only is supported, continuing with concurrency level READ_ONLY", "01M13");
+			resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
+		}
+
+		// check type for supported mode
+		if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) {
+			addWarning("Change sensitive scrolling ResultSet objects are not supported, continuing with a change non-sensitive scrollable cursor.", "01M14");
+			resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE;
+		}
+
+		// check type for supported holdability
+		if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+			addWarning("Close cursors at commit not supported, continuing with holdability to hold open cursors over commit.", "01M15");
+		}
+
+		closed = false;
+		poolable = false;
+	}
+
+	//== methods of interface Statement
+
+	/**
+	 * Adds the given SQL command to the current list of commmands for this
+	 * Statement object.  The commands in this list can be executed as a
+	 * batch by calling the method executeBatch.
+	 *
+	 * @param sql typically this is a static SQL INSERT or UPDATE statement
+	 * @throws SQLException so the PreparedStatement can throw this exception
+	 */
+	@Override
+	public void addBatch(String sql) throws SQLException {
+		batch.add(sql);
+	}
+
+	/**
+	 * Empties this Statement object's current list of SQL commands.
+	 */
+	@Override
+	public void clearBatch() {
+		batch.clear();
+	}
+
+	Lock batchLock = new ReentrantLock();
+	
+	/**
+	 * Submits a batch of commands to the database for execution and if
+	 * all commands execute successfully, returns an array of update
+	 * counts.  The int elements of the array that is returned are
+	 * ordered to correspond to the commands in the batch, which are
+	 * ordered according to the order in which they were added to the
+	 * batch.  The elements in the array returned by the method
+	 * executeBatch may be one of the following:
+	 * <br />
+	 * <ol>
+	 * <li>A number greater than or equal to zero -- indicates that the
+	 * command was processed successfully and is an update count giving
+	 * the number of rows in the database that were affected by the
+	 * command's execution</li>
+	 * <li>A value of SUCCESS_NO_INFO -- indicates that the command was
+	 * processed successfully but that the number of rows affected is
+	 * unknown</li>
+	 * </ol>
+	 * If one of the commands in a batch update fails to execute
+	 * properly, this method throws a BatchUpdateException, and a JDBC
+	 * driver may or may not continue to process the remaining commands
+	 * in the batch.  However, the driver's behavior must be consistent
+	 * with a particular DBMS, either always continuing to process
+	 * commands or never continuing to process commands.
+	 * 
+	 * MonetDB does continues after an error has occurred in the batch.
+	 * If one of the commands attempts to return a result set, an
+	 * SQLException is added to the SQLException list and thrown
+	 * afterwards execution.  Failing queries result in SQLExceptions
+	 * too and may cause subparts of the batch to fail as well.<br />
+	 *
+	 * @return an array of update counts containing one element for each
+	 *         command in the batch.  The elements of the array are ordered
+	 *         according to the order in which commands were added to the
+	 *         batch.
+	 * @throws SQLException if a database access error occurs.  Throws
+	 *         BatchUpdateException (a subclass of SQLException) if one of the
+	 *         commands sent to the database fails to execute properly
+	 */
+	@Override
+	public int[] executeBatch() throws SQLException {
+		// this method is synchronized to make sure noone gets inbetween the
+		// operations we execute below
+
+		batchLock.lock();
+		try {
+			// don't think long if there isn't much to do
+			if (batch.isEmpty()) 
+				return new int[0];
+
+			int[] counts = new int[batch.size()];
+			int offset = 0;
+			boolean first = true;
+			boolean error = false;
+
+			BatchUpdateException e = new BatchUpdateException("Error(s) occurred while executing the batch, see next SQLExceptions for details", "22000", counts);
+			StringBuilder tmpBatch = new StringBuilder(MapiSocket.BLOCK);
+			String sep = connection.queryTempl[2];
+			for (int i = 0; i < batch.size(); i++) {
+				String tmp = batch.get(i);
+				if (sep.length() + tmp.length() > MapiSocket.BLOCK) {
+					// The thing is too big.  Way too big.  Since it won't
+					// be optimal anyway, just add it to whatever we have
+					// and continue.
+					if (!first)
+						tmpBatch.append(sep);
+					tmpBatch.append(tmp);
+					// send and receive
+					error |= internalBatch(tmpBatch.toString(), counts, offset, i + 1, e);
+					offset = i;
+					tmpBatch.delete(0, tmpBatch.length());
+					first = true;
+					continue;
+				}
+				if (tmpBatch.length() + sep.length() + tmp.length() >= MapiSocket.BLOCK) {
+					// send and receive
+					error |= internalBatch(tmpBatch.toString(), counts, offset, i + 1, e);
+					offset = i;
+					tmpBatch.delete(0, tmpBatch.length());
+					first = true;
+				}
+				if (!first) tmpBatch.append(sep);
+				first = false;
+				tmpBatch.append(tmp);
+			}
+			// send and receive
+			error |= internalBatch(tmpBatch.toString(), counts, offset, counts.length, e);
+
+			// throw BatchUpdateException if it contains something
+			if (error)
+				throw e;
+			// otherwise just return the counts
+			return counts;
+		} finally {
+			batchLock.unlock();
+		}
+	}
+
+	private boolean internalBatch(
+			String batch,
+			int[] counts,
+			int offset,
+			int max,
+			BatchUpdateException e)
+		throws BatchUpdateException
+	{
+		try {
+			boolean type = internalExecute(batch);
+			int count = -1;
+			if (!type) count = getUpdateCount();
+			do {
+				if (offset >= max) throw
+					new SQLException("Overflow: don't use multi statements when batching (" + max + ")", "M1M16");
+				if (type) {
+					e.setNextException(
+						new SQLException("Batch query produced a ResultSet! " +
+							"Ignoring and setting update count to " +
+							"value " + EXECUTE_FAILED, "M1M17"));
+					counts[offset] = EXECUTE_FAILED;
+				} else if (count >= 0) {
+					counts[offset] = count;
+				}
+				offset++;
+			} while ((type = getMoreResults()) ||
+					(count = getUpdateCount()) != -1);
+		} catch (SQLException ex) {
+			e.setNextException(ex);
+			for (; offset < max; offset++) {
+				counts[offset] = EXECUTE_FAILED;
+			}
+			return true;
+		}
+		return false;
+	}
+
+	/**
+     * Cancels this Statement object if both the DBMS and driver support
+	 * aborting an SQL statement.  This method can be used by one thread to
+	 * cancel a statement that is being executed by another thread.
+	 *
+	 * @throws SQLException if a database access error occurs or the cancel
+	 *                      operation is not supported
+	 */
+	@Override
+	public void cancel() throws SQLException {
+		throw new SQLException("Query cancelling is currently not supported by the DBMS.", "0A000");
+	}
+
+	/**
+	 * Clears all warnings reported for this Statement object. After a call to
+	 * this method, the method getWarnings returns null until a new warning is
+	 * reported for this Statement object.
+	 */
+	@Override
+	public void clearWarnings() {
+		warnings = null;
+	}
+
+	/**
+	 * Releases this Statement object's database and JDBC resources immediately
+	 * instead of waiting for this to happen when it is automatically closed. It
+	 * is generally good practice to release resources as soon as you are
+	 * finished with them to avoid tying up database resources.
+	 * 
+	 * Calling the method close on a Statement object that is already closed has
+	 * no effect.
+	 * 
+	 * A Statement object is automatically closed when it is garbage collected.
+	 * When a Statement object is closed, its current ResultSet object, if one
+	 * exists, is also closed.
+	 */
+	@Override
+	public void close() {
+		// close previous ResultSet, if not closed already
+		if (lastResponseList != null) lastResponseList.close();
+		closed = true;
+	}
+
+	// Chapter 13.1.2.3 of Sun's JDBC 3.0 Specification
+	/**
+	 * Executes the given SQL statement, which may return multiple results. In
+	 * some (uncommon) situations, a single SQL statement may return multiple
+	 * result sets and/or update counts. Normally you can ignore this unless
+	 * you are (1) executing a stored procedure that you know may return
+	 * multiple results or (2) you are dynamically executing an unknown SQL
+	 * string.
+	 * 
+	 * The execute method executes an SQL statement and indicates the form of
+	 * the first result. You must then use the methods getResultSet or
+	 * getUpdateCount to retrieve the result, and getMoreResults to move to any
+	 * subsequent result(s).
+	 *
+	 * @param sql any SQL statement
+	 * @return true if the first result is a ResultSet object; false if it is an
+	 *         update count or there are no results
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public boolean execute(String sql) throws SQLException {
+		return internalExecute(sql);
+	}
+	
+	/**
+	 * Executes the given SQL statement, which may return multiple
+	 * results, and signals the driver that any auto-generated keys
+	 * should be made available for retrieval.  The driver will ignore
+	 * this signal if the SQL statement is not an INSERT statement.
+	 * 
+	 * In some (uncommon) situations, a single SQL statement may return
+	 * multiple result sets and/or update counts. Normally you can
+	 * ignore this unless you are (1) executing a stored procedure that
+	 * you know may return multiple results or (2) you are dynamically
+	 * executing an unknown SQL string.
+	 * 
+	 * The execute method executes an SQL statement and indicates the
+	 * form of the first result. You must then use the methods
+	 * getResultSet or getUpdateCount to retrieve the result, and
+	 * getMoreResults to move to any subsequent result(s). 
+	 *
+	 * @param sql any SQL statement
+	 * @param autoGeneratedKeys a constant indicating whether
+	 *        auto-generated keys should be made available for retrieval
+	 *        using the method getGeneratedKeys; one of the following
+	 *        constants: Statement.RETURN_GENERATED_KEYS or
+	 *        Statement.NO_GENERATED_KEYS
+	 * @return true if the first result is a ResultSet  object; false if
+	 *         it is an update count or there are no results
+	 * @throws SQLException - if a database access error occurs or the
+	 *         second parameter supplied to this method is not
+	 *         Statement.RETURN_GENERATED_KEYS or
+	 *         Statement.NO_GENERATED_KEYS.
+	 */
+	@Override
+	public boolean execute(String sql, int autoGeneratedKeys)
+		throws SQLException
+	{
+		if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS &&
+				autoGeneratedKeys != Statement.NO_GENERATED_KEYS)
+			throw new SQLException("Invalid argument, expected RETURN_GENERATED_KEYS or NO_GENERATED_KEYS", "M1M05");
+		
+		/* MonetDB has no way to disable this, so just do the normal
+		 * thing ;) */
+		return internalExecute(sql);
+	}
+
+	/**
+	 * Executes the given SQL statement, which may return multiple
+	 * results, and signals the driver that the auto-generated keys
+	 * indicated in the given array should be made available for
+	 * retrieval. This array contains the indexes of the columns in the
+	 * target table that contain the auto-generated keys that should be
+	 * made available. The driver will ignore the array if the given SQL
+	 * statement is not an INSERT statement.
+	 * 
+	 * Under some (uncommon) situations, a single SQL statement may
+	 * return multiple result sets and/or update counts. Normally you
+	 * can ignore this unless you are (1) executing a stored procedure
+	 * that you know may return multiple results or (2) you are
+	 * dynamically executing an unknown SQL string.
+	 * 
+	 * The execute method executes an SQL statement and indicates the
+	 * form of the first result. You must then use the methods
+	 * getResultSet or getUpdateCount  to retrieve the result, and
+	 * getMoreResults to move to any subsequent result(s).
+	 * 
+	 * MonetDB only supports returing the generated key for one column,
+	 * which will be the first column that has a serial.  Hence, this
+	 * method cannot work as required and the driver will fall back to
+	 * executing with request to the database to return the generated
+	 * key, if any.
+	 * 
+	 * @param sql any SQL statement
+	 * @param columnIndexes an array of the indexes of the columns in
+	 *        the inserted row that should be made available for
+	 *        retrieval by a call to the method getGeneratedKeys
+	 * @return true if the first result is a ResultSet object; false if
+	 *         it is an update count or there are no results
+	 * @throws SQLException if a database access error occurs or the
+	 *         elements in the int array passed to this method are not
+	 *         valid column indexes
+	 */
+	@Override
+	public boolean execute(String sql, int[] columnIndexes)
+		throws SQLException
+	{
+		addWarning("execute: generated keys for fixed set of columns not supported", "01M18");
+		return execute(sql, Statement.RETURN_GENERATED_KEYS);
+	}
+
+	/**
+	 * Executes the given SQL statement, which may return multiple
+	 * results, and signals the driver that the auto-generated keys
+	 * indicated in the given array should be made available for
+	 * retrieval. This array contains the names of the columns in the
+	 * target table that contain the auto-generated keys that should be
+	 * made available. The driver will ignore the array if the given SQL
+	 * statement is not an INSERT statement.
+	 * 
+	 * In some (uncommon) situations, a single SQL statement may return
+	 * multiple result sets and/or update counts. Normally you can
+	 * ignore this unless you are (1) executing a stored procedure that
+	 * you know may return multiple results or (2) you are dynamically
+	 * executing an unknown SQL string.
+	 * 
+	 * The execute method executes an SQL statement and indicates the
+	 * form of the first result. You must then use the methods
+	 * getResultSet or getUpdateCount  to retrieve the result, and
+	 * getMoreResults to move to any subsequent result(s).
+	 * 
+	 * MonetDB only supports returing the generated key for one column,
+	 * which will be the first column that has a serial.  Hence, this
+	 * method cannot work as required and the driver will fall back to
+	 * executing with request to the database to return the generated
+	 * key, if any.
+	 *
+	 * @param sql any SQL statement
+	 * @param columnNames an array of the names of the columns in the
+	 *        inserted row that should be made available for retrieval
+	 *        by a call to the method getGeneratedKeys
+	 * @return true if the next result is a ResultSet object; false if
+	 *         it is an update count or there are no more results
+	 * @throws SQLException if a database access error occurs or the
+	 *         elements of the String array passed to this method are
+	 *         not valid column names
+	 */
+	@Override
+	public boolean execute(String sql, String[] columnNames)
+		throws SQLException
+	{
+		addWarning("execute: generated keys for fixed set of columns not supported", "01M18");
+		return execute(sql, Statement.RETURN_GENERATED_KEYS);
+	}
+
+	/**
+	 * Performs the steps to execute a given SQL statement.  This method
+	 * exists to allow the functionality of this function to be called
+	 * from within this class only.  The PreparedStatement for example
+	 * overrides the execute() method to throw an SQLException, but it
+	 * needs its functionality when the executeBatch method (which is
+	 * inherited) is called.
+	 *
+	 * @param sql any SQL statement
+	 * @return true if the first result is a ResultSet object; false if
+	 *         it is an update count or there are no results
+	 * @throws SQLException if a database access error occurs
+	 */
+	private boolean internalExecute(String sql) throws SQLException {
+		// close previous query, if not closed already
+		if (lastResponseList != null) {
+			lastResponseList.close();
+			lastResponseList = null;
+		}
+
+		// create a container for the result
+		lastResponseList = connection.new ResponseList(
+			fetchSize,
+			maxRows,
+			resultSetType,
+			resultSetConcurrency
+		);
+		// fill the header list by processing the query
+		lastResponseList.processQuery(sql);
+
+		return getMoreResults();
+	}
+
+	/**
+	 * Executes the given SQL statement, which returns a single ResultSet
+	 * object.
+	 *
+	 * @param sql an SQL statement to be sent to the database, typically a
+	 *        static SQL SELECT statement
+	 * @return a ResultSet object that contains the data produced by the given
+	 *         query; never null
+	 * @throws SQLException if a database access error occurs or the given SQL
+	 *         statement produces anything other than a single ResultSet object
+	 */
+	@Override
+	public ResultSet executeQuery(String sql) throws SQLException {
+		if (execute(sql) != true)
+			throw new SQLException("Query did not produce a result set", "M1M19");
+
+		return getResultSet();
+	}
+
+	/**
+	 * Executes the given SQL statement, which may be an INSERT, UPDATE, or
+	 * DELETE statement or an SQL statement that returns nothing, such as an
+	 * SQL DDL statement.
+	 *
+	 * @param sql an SQL INSERT, UPDATE or DELETE statement or an SQL statement
+	 *        that returns nothing
+	 * @return either the row count for INSERT, UPDATE  or DELETE statements, or
+	 *         0 for SQL statements that return nothing<br />
+	 * @throws SQLException if a database access error occurs or the given SQL
+	 *         statement produces a ResultSet object
+	 */
+	@Override
+	public int executeUpdate(String sql) throws SQLException {
+		if (execute(sql) != false)
+			throw new SQLException("Query produced a result set", "M1M17");
+
+		return getUpdateCount();
+	}
+
+	/**
+	 * Executes the given SQL statement and signals the driver with the
+	 * given flag about whether the auto-generated keys produced by this
+	 * Statement object should be made available for retrieval.
+	 *
+	 * @param sql must be an SQL INSERT, UPDATE or DELETE statement or
+	 *        an SQL statement that returns nothing
+	 * @param autoGeneratedKeys - a flag indicating whether
+	 *        auto-generated keys should be made available for
+	 *        retrieval; one of the following constants:
+	 *        Statement.RETURN_GENERATED_KEYS
+	 *        Statement.NO_GENERATED_KEYS
+	 * @return either the row count for INSERT, UPDATE  or DELETE
+	 *         statements, or 0 for SQL statements that return nothing
+	 * @throws SQLException if a database access error occurs, the
+	 *         given SQL statement returns a ResultSet object, or the
+	 *         given constant is not one of those allowed
+	 */
+	@Override
+	public int executeUpdate(String sql, int autoGeneratedKeys)
+		throws SQLException
+	{
+		if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS &&
+				autoGeneratedKeys != Statement.NO_GENERATED_KEYS)
+			throw new SQLException("Invalid argument, expected RETURN_GENERATED_KEYS or NO_GENERATED_KEYS", "M1M05");
+		
+		/* MonetDB has no way to disable this, so just do the normal
+		 * thing ;) */
+		if (execute(sql) != false)
+			throw new SQLException("Query produced a result set", "M1M17");
+
+		return getUpdateCount();
+	}
+
+	/**
+	 * Executes the given SQL statement and signals the driver that the
+	 * auto-generated keys indicated in the given array should be made
+	 * available for retrieval. The driver will ignore the array if the
+	 * SQL statement is not an INSERT statement.
+	 * 
+	 * MonetDB only supports returing the generated key for one column,
+	 * which will be the first column that has a serial.  Hence, this
+	 * method cannot work as required and the driver will fall back to
+	 * executing with request to the database to return the generated
+	 * key, if any.
+	 *
+	 * @param sql an SQL INSERT, UPDATE or DELETE statement or an SQL
+	 *        statement that returns nothing, such as an SQL DDL statement
+	 * @param columnIndexes an array of column indexes indicating the
+	 *        columns that should be returned from the inserted row
+	 * @return either the row count for INSERT, UPDATE, or DELETE
+	 *         statements, or 0 for SQL statements that return nothing
+	 * @throws SQLException if a database access error occurs, the SQL
+	 *         statement returns a ResultSet object, or the second
+	 *         argument supplied to this method is not an int array
+	 *         whose elements are valid column indexes
+	 */
+	@Override
+	public int executeUpdate(String sql, int[] columnIndexes)
+		throws SQLException
+	{
+		addWarning("executeUpdate: generated keys for fixed set of columns not supported", "01M18");
+		return executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
+	}
+
+	/**
+	 * Executes the given SQL statement and signals the driver that the
+	 * auto-generated keys indicated in the given array should be made
+	 * available for retrieval. The driver will ignore the array if the
+	 * SQL statement is not an INSERT statement.
+	 * 
+	 * MonetDB only supports returing the generated key for one column,
+	 * which will be the first column that has a serial.  Hence, this
+	 * method cannot work as required and the driver will fall back to
+	 * executing with request to the database to return the generated
+	 * key, if any.
+	 *
+	 * @param sql an SQL INSERT, UPDATE or DELETE statement or an SQL
+	 *        statement that returns nothing, such as an SQL DDL statement
+	 * @param columnNames an array of the names of the columns that
+	 *        should be returned from the inserted row
+	 * @return either the row count for INSERT, UPDATE, or DELETE
+	 *         statements, or 0 for SQL statements that return nothing
+	 * @throws SQLException if a database access error occurs, the SQL
+	 *         statement returns a ResultSet object, or the second
+	 *         argument supplied to this method is not a String array
+	 *         whose elements are valid column names
+	 */
+	@Override
+	public int executeUpdate(String sql, String[] columnNames)
+		throws SQLException
+	{
+		addWarning("executeUpdate: generated keys for fixed set of columns not supported", "01M18");
+		return executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
+	}
+
+	/**
+	 * Retrieves the Connection object that produced this Statement object.
+	 *
+	 * @return the connection that produced this statement
+	 */
+	@Override
+	public Connection getConnection() {
+		return connection;
+	}
+
+	/**
+	 * Retrieves the direction for fetching rows from database tables that is
+	 * the default for result sets generated from this Statement object. If
+	 * this Statement object has not set a fetch direction by calling the
+	 * method setFetchDirection, the return value is ResultSet.FETCH_FORWARD.
+	 *
+	 * @return the default fetch direction for result sets generated from this
+	 *         Statement object
+	 */
+	@Override
+	public int getFetchDirection() {
+		return fetchDirection;
+	}
+
+	/**
+	 * Retrieves the number of result set rows that is the default fetch size
+	 * for ResultSet objects generated from this Statement object. If this
+	 * Statement object has not set a fetch size by calling the method
+	 * setFetchSize, or the method setFetchSize was called as such to let
+	 * the driver ignore the hint, 0 is returned.
+	 *
+	 * @return the default fetch size for result sets generated from this
+	 *         Statement object
+	 */
+	@Override
+	public int getFetchSize() {
+		return fetchSize;
+	}
+
+	/**
+	 * Retrieves any auto-generated keys created as a result of
+	 * executing this Statement object.  If this Statement object did not
+	 * generate any keys, an empty ResultSet object is returned.
+	 *
+	 * @return a ResultSet object containing the auto-generated key(s)
+	 *         generated by the execution of this Statement object
+	 * @throws SQLException - if a database access error occurs
+	 */
+	@Override
+	public ResultSet getGeneratedKeys() throws SQLException {
+		String[] columns, types;
+		String[][] results;
+
+		columns = new String[1];
+		types = new String[1];
+
+		columns[0] = "GENERATED_KEY";
+		/* the generated key should be an integer, because (wait for it) other 
+		 * frameworks such as spring expect this. */
+		types[0] = "BIGINT";
+
+		if (header instanceof MonetConnection.UpdateResponse) {
+			String lastid = ((MonetConnection.UpdateResponse)header).lastid;
+			if (lastid.equals("-1")) {
+				results = new String[0][1];
+			} else {
+				results = new String[1][1];
+				results[0][0] = lastid;
+			}
+		} else {
+			results = new String[0][1];
+		}
+
+		try {
+			return new MonetVirtualResultSet(this, columns, types, results);
+		} catch (IllegalArgumentException e) {
+			throw new SQLException("Internal driver error: " + e.getMessage(), "M0M03");
+		}
+	}
+
+	/**
+	 * Retrieves the maximum number of bytes that can be returned for
+	 * character and binary column values in a ResultSet object produced
+	 * by this Statement object. This limit applies only to BINARY,
+	 * VARBINARY, LONGVARBINARY, CHAR, VARCHAR, and LONGVARCHAR
+	 * columns. If the limit is exceeded, the excess data is silently
+	 * discarded.
+	 * 
+	 * The MonetDB JDBC driver currently doesn't support limiting
+	 * fieldsizes, and hence always return 0 (unlimited).
+	 *
+	 * @return the current column size limit for columns storing
+	 *         character and binary values; zero means there is no limit
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public int getMaxFieldSize()
+		throws SQLException
+	{
+		return 0;
+	}
+
+	/**
+	 * Retrieves the maximum number of rows that a ResultSet object produced by
+	 * this Statement object can contain. If this limit is exceeded, the excess
+	 * rows are silently dropped.
+	 *
+	 * @return the current maximum number of rows for a ResultSet object
+	 *         produced by this Statement object; zero means there is no limit
+	 */
+	@Override
+	public int getMaxRows() {
+		return maxRows;
+	}
+
+	/**
+	 * Moves to this Statement object's next result, returns true if it is a
+	 * ResultSet object, and implicitly closes any current ResultSet object(s)
+	 * obtained with the method getResultSet.
+	 * 
+	 * There are no more results when the following is true:<br />
+	 * (!getMoreResults() &amp;&amp; (getUpdateCount() == -1)
+	 *
+	 * @return true if the next result is a ResultSet object; false if it is
+	 *              an update count or there are no more results
+	 * @throws SQLException if a database access error occurs
+	 * @see #getMoreResults(int current)
+	 */
+	@Override
+	public boolean getMoreResults() throws SQLException {
+		return getMoreResults(CLOSE_ALL_RESULTS);
+	}
+
+	/**
+	 * Moves to this Statement object's next result, deals with any current
+	 * ResultSet object(s) according to the instructions specified by the given
+	 * flag, and returns true if the next result is a ResultSet object.
+	 * 
+	 * There are no more results when the following is true:<br />
+	 * (!getMoreResults() &amp;&amp; (getUpdateCount() == -1)
+	 *
+	 * @param current one of the following Statement constants indicating what
+	 *                should happen to current ResultSet objects obtained using
+	 *                the method getResultSet: CLOSE_CURRENT_RESULT,
+	 *                KEEP_CURRENT_RESULT, or CLOSE_ALL_RESULTS
+	 * @return true if the next result is a ResultSet object; false if it is
+	 *              an update count or there are no more results
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public boolean getMoreResults(int current) throws SQLException {
+		// protect against people calling this on an unitialised state
+		if (lastResponseList == null) {
+			header = null;
+			return false;
+		}
+
+		if (current == CLOSE_CURRENT_RESULT) {
+			lastResponseList.closeCurrentResponse();
+		} else if (current == CLOSE_ALL_RESULTS) {
+			lastResponseList.closeCurOldResponses();
+		}
+		// we default to keep current result, which requires no action
+		header = lastResponseList.getNextResponse();
+
+		if (header instanceof MonetConnection.ResultSetResponse) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Retrieves the number of seconds the driver will wait for a
+	 * Statement object to execute.  If the limit is exceeded, a
+	 * SQLException is thrown.
+	 * 
+	 * For MonetDB this method always returns zero, as no query
+	 * cancelling is possible.
+	 *
+	 * @return the current query timeout limit in seconds; zero means
+	 *         there is no limit 
+	 * @throws SQLException if a database access error occurs
+	 * @see #setQueryTimeout(int)
+	 */
+	@Override
+	public int getQueryTimeout() throws SQLException {
+		return 0;
+	}
+
+	/**
+	 * Retrieves the current result as a ResultSet object.  This method
+	 * should be called only once per result.
+	 *
+	 * @return the current result as a ResultSet object or null if the result
+	 *         is an update count or there are no more results
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public ResultSet getResultSet() throws SQLException{
+		return (header instanceof MonetConnection.ResultSetResponse) 
+			? new MonetResultSet(this,
+					(MonetConnection.ResultSetResponse)header)
+			: null;
+	}
+
+	/**
+	 * Retrieves the result set concurrency for ResultSet objects generated
+	 * by this Statement object.
+	 *
+	 * @return either ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
+	 */
+	@Override
+	public int getResultSetConcurrency() {
+		return resultSetConcurrency;
+	}
+
+	/**
+	 * Retrieves the result set holdability for ResultSet objects
+	 * generated by this Statement 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 getResultSetHoldability() throws SQLException {
+		return ResultSet.HOLD_CURSORS_OVER_COMMIT;
+	}
+
+	/**
+	 * Retrieves the result set type for ResultSet objects generated by this
+	 * Statement object.
+	 *
+	 * @return one of ResultSet.TYPE_FORWARD_ONLY,
+	 *         ResultSet.TYPE_SCROLL_INSENSITIVE, or
+	 *         ResultSet.TYPE_SCROLL_SENSITIVE
+	 */
+	@Override
+	public int getResultSetType() {
+		return resultSetType;
+	}
+
+	/**
+	 * Retrieves the current result as an update count; if the result is a
+	 * ResultSet object or there are no more results, -1 is returned.  This
+	 * method should be called only once per result.
+	 *
+	 * @return the current result as an update count; -1 if the current result
+	 *         is a ResultSet object or there are no more results
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public int getUpdateCount() throws SQLException {
+		int ret = -1;
+		if (header instanceof MonetConnection.UpdateResponse) {
+			ret = ((MonetConnection.UpdateResponse)header).count;
+		} else if (header instanceof MonetConnection.SchemaResponse) {
+			ret = ((MonetConnection.SchemaResponse)header).state;
+		}
+
+		return ret;
+	}
+
+	/**
+	 * Retrieves the first warning reported by calls on this Statement 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 statement; doing so will cause
+	 * an SQLException to be thrown.
+	 * 
+	 * Note: Subsequent warnings will be chained to this SQLWarning.
+	 *
+	 * @return the first SQLWarning object or null if there are none
+	 * @throws SQLException if a database access error occurs or this method is
+	 *         called on a closed connection
+	 */
+	@Override
+	public SQLWarning getWarnings() throws SQLException {
+		if (closed)
+			throw new SQLException("Cannot call on closed Statement", "M1M20");
+
+		// if there are no warnings, this will be null, which fits with the
+		// specification.
+		return warnings;
+	}
+
+	/**
+	 * Sets the SQL cursor name to the given String, which will be used
+	 * by subsequent Statement object execute methods. This name can
+	 * then be used in SQL positioned update or delete statements to
+	 * identify the current row in the ResultSet object generated by
+	 * this statement. If the database does not support positioned
+	 * update/delete, this method is a noop. To insure that a cursor has
+	 * the proper isolation level to support updates, the cursor's
+	 * SELECT statement should have the form SELECT FOR UPDATE. If FOR
+	 * UPDATE is not present, positioned updates may fail.
+	 * 
+	 * <b>Note:</b> By definition, the execution of positioned updates
+	 * and deletes must be done by a different Statement object than the
+	 * one that generated the ResultSet object being used for
+	 * positioning.  Also, cursor names must be unique within a
+	 * connection. 
+	 * 
+	 * Since MonetDB does not support positioned update/delete, this
+	 * method is a noop.
+	 *
+	 * @param name the new cursor name, which must be unique within a
+	 *        connection
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public void setCursorName(String name) throws SQLException {
+		addWarning("setCursorName: positioned updates/deletes not supported", "01M21");
+	}
+
+	/**
+	 * Sets escape processing on or off. If escape scanning is on (the
+	 * default), the driver will do escape substitution before sending
+	 * the SQL statement to the database. Note: Since prepared
+	 * statements have usually been parsed prior to making this call,
+	 * disabling escape processing for PreparedStatements objects will
+	 * have no effect.
+	 * 
+	 * The MonetDB JDBC driver implements no escape processing at all in
+	 * its current implementation because it is too expensive, and in
+	 * general should not be necessary given SQL standards compliance.
+	 * In this sense, this driver will ignore any call to this function.
+	 *
+	 * @param enable true to enable escape processing; false to disable
+	 *        it
+	 * @throws SQLException if a database access error occurs
+	 */
+	@Override
+	public void setEscapeProcessing(boolean enable) throws SQLException {
+		if (enable)
+			addWarning("setEscapeProcessing: JDBC escape syntax is not supported by this driver", "01M22");
+	}
+
+	/**
+	 * Gives the driver a hint as to the direction in which rows will be
+	 * processed in ResultSet objects created using this Statement object.
+	 * The default value is ResultSet.FETCH_FORWARD.
+	 * 
+	 * Note that this method sets the default fetch direction for result sets
+	 * generated by this Statement object. Each result set has its own methods
+	 * for getting and setting its own fetch direction.
+	 *
+	 * @param direction the initial direction for processing rows
+	 * @throws SQLException if a database access error occurs or the given
+	 *         direction is not one of ResultSet.FETCH_FORWARD,
+	 *         ResultSet.FETCH_REVERSE, or ResultSet.FETCH_UNKNOWN
+	 */
+	@Override
+	public void setFetchDirection(int direction) throws SQLException {
+		if (direction == ResultSet.FETCH_FORWARD ||
+		    direction == ResultSet.FETCH_REVERSE ||
+			direction == ResultSet.FETCH_UNKNOWN)
+		{
+			fetchDirection = direction;
+		} else {
+			throw new SQLException("Illegal direction: " + direction, "M1M05");
+		}
+	}
+
+	/**
+	 * Gives the JDBC driver a hint as to the number of rows that should be
+	 * fetched from the database when more rows are needed. The number of rows
+	 * specified affects only result sets created using this statement. If the
+	 * value specified is zero, then the hint is ignored.
+	 *
+	 * @param rows the number of rows to fetch
+	 * @throws SQLException if the condition 0 &lt;= rows &lt;= this.getMaxRows()
+	 *         is not satisfied.
+	 */
+	@Override
+	public void setFetchSize(int rows) throws SQLException {
+		if (rows >= 0 && !(getMaxRows() != 0 && rows > getMaxRows())) {
+			fetchSize = rows;
+		} else {
+			throw new SQLException("Illegal fetch size value: " + rows, "M1M05");
+		}
+	}
+
+	/**
+	 * Sets the limit for the maximum number of bytes in a ResultSet
+	 * column storing character or binary values to the given number of
+	 * bytes. This limit applies only to BINARY, VARBINARY,
+	 * LONGVARBINARY, CHAR, VARCHAR, and LONGVARCHAR fields. If the
+	 * limit is exceeded, the excess data is silently discarded. For
+	 * maximum portability, use values greater than 256.
+	 * 
+	 * MonetDB does not support any fieldsize limiting, and hence the
+	 * driver does not emulate it either, since it doesn't really lead
+	 * to memory reduction.
+	 *
+	 * @param max the new column size limit in bytes; zero means there
+	 *        is no limit
+	 * @throws SQLException if a database access error occurs or the
+	 *         condition max &gt;= 0 is not satisfied
+	 */
+	@Override
+	public void setMaxFieldSize(int max) throws SQLException {
+		if (max < 0)
+			throw new SQLException("Illegal max value: " + max, "M1M05");
+		if (max > 0)
+			addWarning("setMaxFieldSize: field size limitation not supported", "01M23");
+	}
+
+	/**
+	 * Sets the limit for the maximum number of rows that any ResultSet object
+	 * can contain to the given number. If the limit is exceeded, the excess
+	 * rows are silently dropped.
+	 *
+	 * @param max the new max rows limit; zero means there is no limit
+	 * @throws SQLException if the condition max >= 0 is not satisfied
+	 */
+	@Override
+	public void setMaxRows(int max) throws SQLException {
+		if (max < 0)
+			throw new SQLException("Illegal max value: " + max, "M1M05");
+		maxRows = max;
+	}
+
+	/**
+	 * Sets the number of seconds the driver will wait for a Statement
+	 * object to execute to the given number of seconds. If the limit is
+	 * exceeded, an SQLException is thrown.
+	 * 
+	 * MonetDB does not support cancelling running queries, hence this
+	 * method does not do anything.
+	 *
+	 * @param seconds the new query timeout limit in seconds; zero means
+	 *        there is no limit
+	 * @throws SQLException if a database access error occurs or the
+	 *         condition seconds &gt;= 0 is not satisfied
+	 */
+	@Override
+	public void setQueryTimeout(int seconds) throws SQLException {
+		if (seconds < 0)
+			throw new SQLException("Illegal timeout value: " + seconds, "M1M05");
+		if (seconds > 0)
+			addWarning("setQueryTimeout: query time outs not supported", "01M24");
+	}
+
+	//== 1.6 methods (JDBC 4.0)
+
+	/**
+	 * Retrieves whether this Statement object has been closed. A
+	 * Statement is closed if the method close has been called on it, or
+	 * if it is automatically closed.
+	 *
+	 * @return true if this Statement object is closed; false if it is
+	 *         still open
+	 */
+	@Override
+	public boolean isClosed() {
+		return closed;
+	}
+
+	/**
+	 * Requests that a Statement be pooled or not pooled. The value
+	 * specified is a hint to the statement pool implementation
+	 * indicating whether the applicaiton wants the statement to be
+	 * pooled. It is up to the statement pool manager as to whether the
+	 * hint is used. 
+	 * 
+	 * The poolable value of a statement is applicable to both internal
+	 * statement caches implemented by the driver and external statement
+	 * caches implemented by application servers and other applications.
+	 * 
+	 * By default, a Statement is not poolable when created, and a
+	 * PreparedStatement and CallableStatement are poolable when
+	 * created.
+	 *
+	 * @param poolable requests that the statement be pooled if true
+	 *        and that the statement not be pooled if false
+	 */
+	@Override
+	public void setPoolable(boolean poolable) {
+		this.poolable = poolable;
+	}
+
+	/**
+	 * Returns a value indicating whether the Statement is poolable or
+	 * not.
+	 *
+	 * @return true if the Statement is poolable; false otherwise
+	 */
+	@Override
+	public boolean isPoolable() {
+		return poolable;
+	}
+
+	//== 1.7 methods (JDBC 4.1)
+	
+	/**
+	 * Specifies that this Statement will be closed when all its
+	 * dependent result sets are closed.  If execution of the Statement
+	 * does not produce any result sets, this method has no effect.
+	 *
+	 * @throws SQLException if this method is called on a closed Statement
+	 */
+	@Override
+	public void closeOnCompletion() throws SQLException {
+		if (closed)
+			throw new SQLException("Cannot call on closed Statement", "M1M20");
+		closeOnCompletion = true;
+	}
+
+	/**
+	 * Returns a value indicating whether this Statement will be closed
+	 * when all its dependent result sets are closed.
+	 *
+	 * @return true if the Statement will be closed when all of its
+	 *         dependent result sets are closed; false otherwise
+	 * @throws SQLException if this method is called on a closed Statement
+	 */
+	@Override
+	public boolean isCloseOnCompletion() throws SQLException {
+		if (closed)
+			throw new SQLException("Cannot call on closed Statement", "M1M20");
+		return closeOnCompletion;
+	}
+
+	//== end methods of interface Statement
+
+	/**
+	 * Adds a warning to the pile of warnings this Statement object has. If
+	 * there were no warnings (or clearWarnings was called) this warning will
+	 * be the first, otherwise this warning will get appended to the current
+	 * warning.
+	 *
+	 * @param reason the warning message
+	 */
+	private void addWarning(String reason, String sqlstate) {
+		if (warnings == null) {
+			warnings = new SQLWarning(reason, sqlstate);
+		} else {
+			warnings.setNextWarning(new SQLWarning(reason, sqlstate));
+		}
+	}
+
+	/**
+	 * Closes this Statement if there are no more open ResultSets
+	 * (Responses).  Called by MonetResultSet.close().
+	 */
+	void closeIfCompletion() {
+		if (!closeOnCompletion || lastResponseList == null)
+			return;
+		if (!lastResponseList.hasUnclosedResponses())
+			close();
+	}
+}
+
+/**
+ * This internal subclass is not intended for normal use. Therefore it is restricted to
+ * classes from the very same package only.
+ *
+ * Known issues with this class: some methods of the ResultSetMetaData object (obtained via getMetaData())
+ * require that its statement argument (accessed via getStatement()) has a valid Statement object set.
+ * Instances of this subclass do not have a valid Statement (see special constructor), so
+ * those metadata methods do not return the correct values.
+ * Special checks are programmed to prevent NullPointerExceptions, see above.
+ *
+ * As of Jun2016 this class is only used by MonetStatement.getGeneratedKeys()
+ * Note: to resolve a javac -Xlint warning, this class definition is moved to this file.
+ *
+ * TODO: try to eliminate the need for this class completely.
+ */
+class MonetVirtualResultSet extends MonetResultSet {
+	private String results[][];
+	private boolean closed;
+
+	MonetVirtualResultSet(
+		Statement statement,
+		String[] columns,
+		String[] types,
+		String[][] results
+	) throws IllegalArgumentException {
+		super(statement, columns, types, results.length);
+
+		this.results = results;
+		closed = false;
+	}
+
+	/**
+	 * This method is overridden in order to let it use the results array
+	 * instead of the cache in the Statement object that created it.
+	 *
+	 * @param row the number of the row to which the cursor should move. A
+	 *        positive number indicates the row number counting from the
+	 *        beginning of the result set; a negative number indicates the row
+	 *        number counting from the end of the result set
+	 * @return true if the cursor is on the result set; false otherwise
+	 * @throws SQLException if a database error occurs
+	 */
+ 	@Override
+	public boolean absolute(int row) throws SQLException {
+		if (closed)
+			throw new SQLException("ResultSet is closed!", "M1M20");
+
+		// first calculate what the JDBC row is
+		if (row < 0) {
+			// calculate the negatives...
+			row = tupleCount + row + 1;
+		}
+		// now place the row not farther than just before or after the result
+		if (row < 0) row = 0;	// before first
+		else if (row > tupleCount + 1) row = tupleCount + 1;	// after last
+
+		// store it
+		curRow = row;
+
+		// see if we have the row
+		if (row < 1 || row > tupleCount) return false;
+
+		for (int i = 0; i < results[row - 1].length; i++) {
+			tlp.values[i] = results[row - 1][i];
+		}
+
+		return true;
+	}
+
+	/**
+	 * Mainly here to prevent errors when the close method is called. There
+	 * is no real need for this object to close it. We simply remove our
+	 * resultset data.
+	 */
+	@Override
+	public void close() {
+		if (!closed) {
+			closed = true;
+			results = null;
+			// types and columns are MonetResultSets private parts
+		}
+	}
+}