Mercurial > hg > monetdb-java
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() && (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() && (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 <= rows <= 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 >= 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 >= 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 + } + } +}