Mercurial > hg > monetdb-java
view src/main/java/org/monetdb/jdbc/MonetStatement.java @ 970:f90d811e97eb default tip
Adjust getTableTypes() test for new table type: LOCAL TEMPORARY VIEW, added in 11.53.4 (Mar2025-SP1)
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Thu, 03 Apr 2025 15:01:33 +0200 (4 days ago) |
parents | 5cc071c5c170 |
children |
line wrap: on
line source
/* * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2024, 2025 MonetDB Foundation; * Copyright August 2008 - 2023 MonetDB B.V.; * Copyright 1997 - July 2008 CWI. */ package org.monetdb.jdbc; import org.monetdb.mcl.net.MapiSocket; import java.sql.BatchUpdateException; import java.sql.Connection; import java.sql.Statement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLWarning; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; /** *<pre> * A {@link Statement} suitable for the MonetDB database. * * The object used for executing a static SQL statement and returning * the result(s) it produces. * * 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 returns the affected rows for DML. * Commit and rollback are implemented, as is the autoCommit mechanism * which relies on server side auto commit. * Multi-result queries are supported using the getMoreResults() method. *</pre> * * @author Fabian Groffen * @author Martin van Dinther * @version 0.9 */ public class MonetStatement extends MonetWrapper implements Statement, AutoCloseable { /** The parental Connection object */ protected final 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 = false; /** Whether the application wants this Statement object to be pooled */ protected boolean poolable = false; /** Whether this Statement should be closed if the last ResultSet closes */ private boolean closeOnCompletion = false; /** The timeout (in sec) for the query to return, 0 means no timeout */ private int queryTimeout; /** The size of the blocks of results to ask for at the server */ private int fetchSize; /** The maximum number of rows to return in a ResultSet, 0 indicates unlimited */ private long maxRows; /** The type of ResultSet to produce; i.e. forward only, random access */ private int resultSetType = MonetResultSet.DEF_RESULTSETTYPE; /** The suggested direction of fetching data (implemented but not used) */ private int fetchDirection = MonetResultSet.DEF_FETCHDIRECTION; /** The concurrency of the ResultSet to produce */ private int resultSetConcurrency = MonetResultSet.DEF_CONCURRENCY; /** A List to hold all queries of a batch */ private ArrayList<String> batch; private ReentrantLock batchLock; /** * MonetStatement constructor which checks the arguments for validity. * 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 * @param resultSetHoldability holdability of ResultSet after commit * @throws SQLException if an error occurs during login * @throws IllegalArgumentException is one of the arguments null or empty */ MonetStatement( final MonetConnection connection, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException, IllegalArgumentException { if (connection == null) throw new IllegalArgumentException("No Connection given!"); this.connection = connection; this.queryTimeout = connection.lastSetQueryTimeout; this.fetchSize = connection.getDefaultFetchSize(); this.resultSetType = resultSetType; this.resultSetConcurrency = resultSetConcurrency; // check our limits, and generate warnings as appropriate if (this.resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) { addWarning("No concurrency mode other then read only is supported, continuing with concurrency level READ_ONLY", "01M13"); this.resultSetConcurrency = ResultSet.CONCUR_READ_ONLY; } // check type for supported mode if (this.resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) { addWarning("Change sensitive scrolling ResultSet objects are not supported, continuing with a change non-sensitive scrollable cursor.", "01M14"); this.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 cursors open over commit.", "01M15"); } } //== methods of interface Statement /** * Adds the given SQL command to the current list of commands 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(final String sql) throws SQLException { if (sql == null || sql.isEmpty()) throw new SQLException("Missing SQL statement", "M1M05"); if (batch == null) { // create the ArrayList at first time use batch = new ArrayList<String>(); } batch.add(sql); } /** * Empties this Statement object's current list of SQL commands. */ @Override public void clearBatch() { if (batch != null) batch.clear(); } /** * 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: * <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>A value of SUCCESS_NO_INFO -- indicates that the command was * processed successfully but that the number of rows affected is unknown * <p>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. If the driver continues processing after a failure, the array * returned by the method BatchUpdateException.getUpdateCounts will * contain as many elements as there are commands in the batch, and at * least one of the elements will be the following:</p> * <li>A value of EXECUTE_FAILED -- indicates that the command failed to * execute successfully and occurs only if a driver continues to process * commands after a command fails * </ol> * * 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. * * @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 { if (batch == null || batch.isEmpty()) { return new int[0]; } final long[] ret = executeLargeBatch(); // copy contents of long[] into new int[] final int[] counts = new int[ret.length]; for (int i = 0; i < ret.length; i++) { if (ret[i] >= Integer.MAX_VALUE) counts[i] = Integer.MAX_VALUE; else counts[i] = (int)ret[i]; } return counts; } /** * 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 this method is called on a closed Statement * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this method */ @Override public void cancel() throws SQLException { throw new SQLFeatureNotSupportedException("Query cancelling is currently not supported by the driver.", "0A000"); // a request to implement this is already logged, see: https://github.com/MonetDB/MonetDB/issues/6222 } /** * 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() { clearBatch(); clearWarnings(); // close previous ResultSet, if not closed already if (lastResponseList != null) { lastResponseList.close(); lastResponseList = null; } header = null; batchLock = null; 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(final 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(final String sql, final 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 returning 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(final String sql, final 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 returning 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(final String sql, final 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(final String sql) throws SQLException { // close previous query, if not closed already if (lastResponseList != null) { lastResponseList.close(); lastResponseList = null; } if (sql == null || sql.isEmpty()) throw new SQLException("Missing SQL statement", "M1M05"); if (queryTimeout != connection.lastSetQueryTimeout) { // set requested/changed queryTimeout on the server side first connection.setQueryTimeout(queryTimeout); } // 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 query, which returns a single ResultSet object. * * @param sql an SQL query 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(final 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 * @throws SQLException if a database access error occurs or the given SQL * statement produces a ResultSet object */ @Override public int executeUpdate(final String sql) throws SQLException { if (execute(sql) != false) throw new SQLException("Statement produced a result set", "M1M17"); return Math.max(getUpdateCount(), 0); } /** * 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(final String sql, final 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("Statement produced a result set", "M1M17"); return Math.max(getUpdateCount(), 0); } /** * 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 returning 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(final String sql, final 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 returning 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(final String sql, final 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, the return value is implementation-specific. * * @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 { final String[] columns, types; final 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 != null && header instanceof MonetConnection.UpdateResponse) { final 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. * * @return the current column size limit for columns storing * character and binary values; zero means there is no limit * @see #setMaxFieldSize(int max) */ @Override public int getMaxFieldSize() { return 0x7ffffffe; // MonetDB supports null terminated strings of max 2GB, see function: int UTF8_strlen() } /** * 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 * @see #setMaxRows(int max) */ @Override public int getMaxRows() { if (maxRows >= Integer.MAX_VALUE) return Integer.MAX_VALUE; return (int)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: * (!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: * (!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(final int current) throws SQLException { // protect against people calling this on an uninitialized 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(); return (header != null && header instanceof MonetConnection.ResultSetResponse); } /** * Retrieves the number of seconds the driver will wait for a * Statement object to execute. If the limit is exceeded, a * SQLException is thrown. * * @return the current query timeout limit in seconds; zero means * there is no limit * @see #setQueryTimeout(int) */ @Override public int getQueryTimeout() { return queryTimeout; } /** * 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 { if (header != null && header instanceof MonetConnection.ResultSetResponse) return new MonetResultSet(this, (MonetConnection.ResultSetResponse)header); return 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 */ @Override public int getResultSetHoldability() { 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 { long ret = getLargeUpdateCount(); if (ret >= Integer.MAX_VALUE) return Integer.MAX_VALUE; return (int)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(final 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 does not implement scanning and conditional * removal of escape sequences. Newer MonetDB servers (post 11.45) * have the capability to parse and handle JDBC/ODBC escape sequences * but you can not disable it. * * @param enable true to enable escape processing; false to disable it * @throws SQLException if a database access error occurs */ @Override public void setEscapeProcessing(final boolean enable) throws SQLException { // MonetDB releases Sep2022 (11.45) and older do not support JDBC escape processing in the server or driver if ((connection.getDatabaseMajorVersion() == 11) && (connection.getDatabaseMinorVersion() <= 45)) { if (enable) addWarning("setEscapeProcessing(true): JDBC escape syntax is not supported by this driver or server", "01M22"); } else { // For newer servers (post 11.45) it is not possible to turn it off if (! enable) addWarning("setEscapeProcessing(false): Cannot disable JDBC escape processing.", "M1M05"); } } /** * 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(final 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 ResultSet 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(final int rows) throws SQLException { if (rows >= 0 && !(maxRows != 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 * @see #getMaxFieldSize() */ @Override public void setMaxFieldSize(final 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 * @see #getMaxRows() */ @Override public void setMaxRows(final 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. * * @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(final int seconds) throws SQLException { if (seconds < 0) throw new SQLException("Illegal timeout value: " + seconds, "M1M05"); queryTimeout = seconds; } //== 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 application 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(final 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; } //== Java 1.8 methods (JDBC 4.2) /** * 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. * * This method should be used when the returned row count may exceed Integer.MAX_VALUE. * The default implementation will throw UnsupportedOperationException * * @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 or this * method is called on a closed Statement */ @Override public long getLargeUpdateCount() throws SQLException { if (header != null) { if (header instanceof MonetConnection.UpdateResponse) { final long updCnt = ((MonetConnection.UpdateResponse)header).count; if (updCnt >= 0) return updCnt; } } return -1; } /** * Sets the limit for the maximum number of rows that any ResultSet object * generated by this Statement object can contain to the given number. * If the limit is exceeded, the excess rows are silently dropped. * * This method should be used when the row limit may exceed Integer.MAX_VALUE. * The default implementation will throw UnsupportedOperationException * * @param max the new max rows limit; zero means there is no limit * @throws SQLException if a database access error occurs, * this method is called on a closed Statement or * the condition max >= 0 is not satisfied * @see #getLargeMaxRows() */ @Override public void setLargeMaxRows(final long max) throws SQLException { if (max < 0) throw new SQLException("Illegal max value: " + max, "M1M05"); maxRows = max; } /** * 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. * * This method should be used when the returned row limit may exceed Integer.MAX_VALUE. * The default implementation will return 0 * * @return the current maximum number of rows for a ResultSet object * produced by this Statement object; zero means there is no limit * @see #setLargeMaxRows(long max) */ @Override public long getLargeMaxRows() { return maxRows; } /** * Submits a batch of commands to the database for execution and if * all commands execute successfully, returns an array of update counts. * The long 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 executeLargeBatch * may be one of the following: * <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>A value of SUCCESS_NO_INFO -- indicates that the command was * processed successfully but that the number of rows affected is unknown * <p>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. If the driver continues processing after a failure, the array * returned by the method BatchUpdateException.getLargeUpdateCounts will * contain as many elements as there are commands in the batch, and at * least one of the elements will be the following:</p> * <li>A value of EXECUTE_FAILED -- indicates that the command failed to * execute successfully and occurs only if a driver continues to process * commands after a command fails * </ol> * * This method should be used when the returned row count may exceed Integer.MAX_VALUE. * The default implementation will throw UnsupportedOperationException * * 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. * * @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, this method is called * on a closed Statement or the driver does not support batch statements. * @throws BatchUpdateException (a subclass of SQLException) if one of the * commands sent to the database fails to execute properly or * attempts to return a result set. */ @Override public long[] executeLargeBatch() throws SQLException { if (batch == null || batch.isEmpty()) { return new long[0]; } // this method is synchronized/locked to make sure no one gets in between the // operations we execute below if (batchLock == null) { // create a ReentrantLock at first time use batchLock = new ReentrantLock(); } batchLock.lock(); try { final long[] counts = new long[batch.size()]; final String sep = connection.queryTempl[2]; final int sepLen = sep.length(); final BatchUpdateException e = new BatchUpdateException( "Error(s) occurred while executing the batch, " + "see chained SQLExceptions for details", "22000", 22000, counts, null); final StringBuilder tmpBatch = new StringBuilder(MapiSocket.BLOCK); int offset = 0; boolean first = true; boolean error = false; for (int i = 0; i < batch.size(); i++) { String tmp = batch.get(i); if (sepLen + 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, counts, offset, i + 1, e); offset = i; tmpBatch.setLength(0); // clear the buffer first = true; continue; } if (tmpBatch.length() + sepLen + tmp.length() >= MapiSocket.BLOCK) { // send and receive error |= internalBatch(tmpBatch, counts, offset, i + 1, e); offset = i; tmpBatch.setLength(0); // clear the buffer first = true; } if (first) first = false; else tmpBatch.append(sep); tmpBatch.append(tmp); } // send and receive error |= internalBatch(tmpBatch, counts, offset, counts.length, e); // throw BatchUpdateException if it contains something if (error) throw e; // otherwise just return the counts return counts; } finally { batch.clear(); batchLock.unlock(); } } private boolean internalBatch( final StringBuilder batch, final long[] counts, int offset, final int max, final BatchUpdateException e) throws BatchUpdateException { if (batch.length() == 0) return false; try { long count = -1; boolean hasResultSet = internalExecute(batch.toString()); if (!hasResultSet) count = getLargeUpdateCount(); do { if (offset >= max) throw new SQLException("Overflow: don't use multi statements when batching (" + max + ")", "M1M16"); if (hasResultSet) { e.setNextException( new SQLException("Batch query produced a ResultSet! " + "Ignoring and setting update count to value " + EXECUTE_FAILED, "M1M17")); counts[offset] = EXECUTE_FAILED; } else { counts[offset] = (count >= 0) ? count : SUCCESS_NO_INFO; } offset++; } while ((hasResultSet = getMoreResults()) || (count = getLargeUpdateCount()) != -1); } catch (SQLException ex) { e.setNextException(ex); for (; offset < max; offset++) { counts[offset] = EXECUTE_FAILED; } return true; } return false; } /** * 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. * * This method should be used when the returned row count may exceed Integer.MAX_VALUE. * Note: This method cannot be called on a PreparedStatement or CallableStatement. * The default implementation will throw SQLFeatureNotSupportedException * * @param sql an SQL Data Manipulation Language (DML) statement, such as * INSERT, UPDATE or DELETE; or an SQL statement that returns nothing, * such as a DDL statement. * @return either (1) the row count for SQL Data Manipulation Language (DML) statements * or (2) 0 for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is * called on a closed Statement, the given SQL statement produces a * ResultSet object, the method is called on a PreparedStatement or CallableStatement */ @Override public long executeLargeUpdate(final String sql) throws SQLException { if (execute(sql) != false) throw new SQLException("Statement produced a result set", "M1M17"); return Math.max(getLargeUpdateCount(), 0L); } /** * 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. * The driver will ignore the flag if the SQL statement is not an INSERT * statement, or an SQL statement able to return auto-generated keys * (the list of such statements is vendor-specific). * * This method should be used when the returned row count may exceed Integer.MAX_VALUE. * Note: This method cannot be called on a PreparedStatement or CallableStatement. * The default implementation will throw SQLFeatureNotSupportedException * * @param sql an SQL Data Manipulation Language (DML) statement, such as * INSERT, UPDATE or DELETE; or an SQL statement that returns nothing, * such as a DDL statement. * @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 (1) the row count for SQL Data Manipulation Language (DML) statements * or (2) 0 for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is * called on a closed Statement, the given SQL statement produces a * ResultSet object, the given constant is not one of those allowed, * the method is called on a PreparedStatement or CallableStatement */ @Override public long executeLargeUpdate(final String sql, final 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("Statement produced a result set", "M1M17"); return Math.max(getLargeUpdateCount(), 0L); } /** * 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. * * This method should be used when the returned row count may exceed Integer.MAX_VALUE. * Note: This method cannot be called on a PreparedStatement or CallableStatement. * The default implementation will throw SQLFeatureNotSupportedException * * MonetDB only supports returning 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 Data Manipulation Language (DML) statement, such as * INSERT, UPDATE or DELETE; or an SQL statement that returns nothing, * such as a DDL statement. * @param columnIndexes an array of column indexes indicating the * columns that should be returned from the inserted row * @return either (1) the row count for SQL Data Manipulation Language (DML) statements * or (2) 0 for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is * called on a closed Statement, the given SQL statement produces a * ResultSet object, the second argument supplied to this method is * not an int array whose elements are valid column indexes, * the method is called on a PreparedStatement or CallableStatement */ @Override public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException { addWarning("executeLargeUpdate: generated keys for fixed set of columns not supported", "01M18"); return executeLargeUpdate(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. * * This method should be used when the returned row count may exceed Integer.MAX_VALUE. * Note: This method cannot be called on a PreparedStatement or CallableStatement. * The default implementation will throw SQLFeatureNotSupportedException * * MonetDB only supports returning 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 Data Manipulation Language (DML) statement, such as * INSERT, UPDATE or DELETE; or an SQL statement that returns nothing, * such as a DDL statement. * @param columnNames an array of the names of the columns that * should be returned from the inserted row * @return either (1) the row count for SQL Data Manipulation Language (DML) statements * or (2) 0 for SQL statements that return nothing * @throws SQLException if a database access error occurs, this method is * called on a closed Statement, the given SQL statement produces a * ResultSet object, the second argument supplied to this method is * not a String array whose elements are valid column names, * the method is called on a PreparedStatement or CallableStatement */ @Override public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException { addWarning("executeLargeUpdate: generated keys for fixed set of columns not supported", "01M18"); return executeLargeUpdate(sql, Statement.RETURN_GENERATED_KEYS); } //== end methods of interface Statement //== internal helper methods which do not belong to the JDBC interface /** * 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 * @param sqlstate the SQLState code (5 characters) */ private void addWarning(final String reason, final String sqlstate) { SQLWarning warng = new SQLWarning(reason, sqlstate); if (warnings == null) { warnings = warng; } else { warnings.setNextWarning(warng); } } /** * 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. */ final class MonetVirtualResultSet extends MonetResultSet { private String results[][]; private boolean closed; MonetVirtualResultSet( final Statement statement, final String[] columns, final String[] types, final 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 = (int) tupleCount + row + 1; } // now place the row not farther than just before or after the result if (row < 0) row = 0; // before first else if (row > tupleCount + 1) row = (int) tupleCount + 1; // after last // store it curRow = row; // see if we have the row if (row < 1 || row > tupleCount) return false; System.arraycopy(results[row - 1], 0, tlp.values, 0, results[row - 1].length); 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() { results = null; closed = true; super.close(); } }