/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.jdbc; import java.io.IOException; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import org.voltdb.client.ClientStats; import org.voltdb.client.ClientStatsContext; public class JDBC4Connection implements java.sql.Connection, IVoltDBConnection { public static final String COMMIT_THROW_EXCEPTION = "jdbc.committhrowexception"; public static final String ROLLBACK_THROW_EXCEPTION = "jdbc.rollbackthrowexception"; public static final String QUERYTIMEOUT_UNIT = "jdbc.querytimeout.unit"; protected final JDBC4ClientConnection NativeConnection; protected final String User; protected TimeUnit queryTimeOutUnit = TimeUnit.SECONDS; private boolean isClosed = false; private Properties props; private boolean autoCommit = true; public JDBC4Connection(JDBC4ClientConnection connection, Properties props) { this.NativeConnection = connection; this.props = props; this.User = this.props.getProperty("user", ""); if (this.props.getProperty(JDBC4Connection.QUERYTIMEOUT_UNIT, "Seconds").equalsIgnoreCase("milliseconds")) { this.queryTimeOutUnit = TimeUnit.MILLISECONDS; } } private void checkClosed() throws SQLException { if (this.isClosed()) throw SQLError.get(SQLError.CONNECTION_CLOSED); } // Clears all warnings reported for this Connection object. @Override public void clearWarnings() throws SQLException { checkClosed(); } // Releases this Connection object's database and JDBC resources immediately instead of waiting for them to be automatically released. @Override public void close() throws SQLException { try { isClosed = true; JDBC4ClientConnectionPool.dispose(NativeConnection); } catch(Exception x) { throw SQLError.get(x); } } // Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object. @Override public void commit() throws SQLException { checkClosed(); if (props.getProperty(COMMIT_THROW_EXCEPTION, "true").equalsIgnoreCase("true")) { throw SQLError.noSupport(); } } // Factory method for creating Array objects. @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Constructs an object that implements the Blob interface. @Override public Blob createBlob() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Constructs an object that implements the Clob interface. @Override public Clob createClob() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Constructs an object that implements the NClob interface. @Override public NClob createNClob() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Constructs an object that implements the SQLXML interface. @Override public SQLXML createSQLXML() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Creates a Statement object for sending SQL statements to the database. @Override public Statement createStatement() throws SQLException { checkClosed(); try { return new JDBC4Statement(this); } catch(Exception x) { throw SQLError.get(x); } } /** * Check if the createStatement() options are supported * * See http://docs.oracle.com/javase/7/docs/api/index.html?java/sql/DatabaseMetaData.html * * The following flags are supported: * - The type must either be TYPE_SCROLL_INSENSITIVE or TYPE_FORWARD_ONLY. * - The concurrency must be CONCUR_READ_ONLY. * - The holdability must be CLOSE_CURSORS_AT_COMMIT. * * @param resultSetType JDBC result set type option * @param resultSetConcurrency JDBC result set concurrency option * @param resultSetHoldability JDBC result set holdability option * @throws SQLException if not supported */ private static void checkCreateStatementSupported( int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { if ( ( (resultSetType != ResultSet.TYPE_SCROLL_INSENSITIVE && resultSetType != ResultSet.TYPE_FORWARD_ONLY)) || resultSetConcurrency != ResultSet.CONCUR_READ_ONLY || resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) { throw SQLError.noSupport(); } } /** * Check if the createStatement() options are supported * * The following flags are supported: * - The type must either be TYPE_SCROLL_INSENSITIVE or TYPE_FORWARD_ONLY. * - The concurrency must be CONCUR_READ_ONLY. * * @param resultSetType JDBC result set type option * @param resultSetConcurrency JDBC result set concurrency option * @throws SQLException if not supported */ private static void checkCreateStatementSupported( int resultSetType, int resultSetConcurrency) throws SQLException { checkCreateStatementSupported(resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT); } // Creates a Statement object that will generate ResultSet objects with the given type and concurrency. @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { checkClosed(); // Reject options that don't coincide with normal VoltDB behavior. checkCreateStatementSupported(resultSetType, resultSetConcurrency); return createStatement(); } // Creates a Statement object that will generate ResultSet objects with the given type, concurrency, and holdability. @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkClosed(); // Reject options that don't coincide with normal VoltDB behavior. checkCreateStatementSupported(resultSetType, resultSetConcurrency, resultSetHoldability); return createStatement(); } // Factory method for creating Struct objects. @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Retrieves the current auto-commit mode for this Connection object. // We are always auto-committing, but if let's be consistent with the lying. @Override public boolean getAutoCommit() throws SQLException { checkClosed(); return autoCommit; } // Retrieves this Connection object's current catalog name. @Override public String getCatalog() throws SQLException { checkClosed(); return ""; } // Returns a list containing the name and current value of each client info property supported by the driver. @Override public Properties getClientInfo() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Returns the value of the client info property specified by name. @Override public String getClientInfo(String name) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Retrieves the current holdability of ResultSet objects created using this Connection object. @Override public int getHoldability() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Retrieves a DatabaseMetaData object that contains metadata about the database to which this Connection object represents a connection. @Override public DatabaseMetaData getMetaData() throws SQLException { checkClosed(); return new JDBC4DatabaseMetaData(this); } // Retrieves this Connection object's current transaction isolation level. @Override public int getTransactionIsolation() throws SQLException { checkClosed(); return TRANSACTION_SERIALIZABLE; } // Retrieves the Map object associated with this Connection object. @Override public Map<String,Class<?>> getTypeMap() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Retrieves the first warning reported by calls on this Connection object. @Override public SQLWarning getWarnings() throws SQLException { checkClosed(); return null; } // Retrieves whether this Connection object has been closed. @Override public boolean isClosed() throws SQLException { return isClosed; } // Retrieves whether this Connection object is in read-only mode. @Override public boolean isReadOnly() throws SQLException { checkClosed(); return false; } // Returns true if the connection has not been closed and is still valid. @Override public boolean isValid(int timeout) throws SQLException { return !isClosed; } // Converts the given SQL statement into the system's native SQL grammar. @Override public String nativeSQL(String sql) throws SQLException { checkClosed(); return sql; // Well... } // Creates a CallableStatement object for calling database stored procedures. @Override public CallableStatement prepareCall(String sql) throws SQLException { checkClosed(); return new JDBC4CallableStatement(this, sql); } // Creates a CallableStatement object that will generate ResultSet objects with the given type and concurrency. @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { if (resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE && resultSetConcurrency == ResultSet.CONCUR_READ_ONLY) return prepareCall(sql); checkClosed(); throw SQLError.noSupport(); } // Creates a CallableStatement object that will generate ResultSet objects with the given type and concurrency. @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Creates a PreparedStatement object for sending parameterized SQL statements to the database. @Override public PreparedStatement prepareStatement(String sql) throws SQLException { checkClosed(); return new JDBC4PreparedStatement(this, sql); } // Creates a default PreparedStatement object that has the capability to retrieve auto-generated keys. @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Creates a default PreparedStatement object capable of returning the auto-generated keys designated by the given array. @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Creates a PreparedStatement object that will generate ResultSet objects with the given type and concurrency. @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { if ((resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE || resultSetType == ResultSet.TYPE_FORWARD_ONLY) && resultSetConcurrency == ResultSet.CONCUR_READ_ONLY) { return prepareStatement(sql); } checkClosed(); throw SQLError.noSupport(); } // Creates a PreparedStatement object that will generate ResultSet objects with the given type, concurrency, and holdability. @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Creates a default PreparedStatement object capable of returning the auto-generated keys designated by the given array. @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Removes the specified Savepoint and subsequent Savepoint objects from the current transaction. @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Undoes all changes made in the current transaction and releases any database locks currently held by this Connection object. @Override public void rollback() throws SQLException { checkClosed(); if (props.getProperty(ROLLBACK_THROW_EXCEPTION, "true").equalsIgnoreCase("true")) { throw SQLError.noSupport(); } } // Undoes all changes made after the given Savepoint object was set. @Override public void rollback(Savepoint savepoint) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Sets this connection's auto-commit mode to the given state. @Override public void setAutoCommit(boolean autoCommit) throws SQLException { checkClosed(); // Always true - error out only if the client is trying to set somethign else if (!autoCommit && (props.getProperty(COMMIT_THROW_EXCEPTION, "true").equalsIgnoreCase("true"))) { throw SQLError.noSupport(); } else { this.autoCommit = autoCommit; } } // Sets the given catalog name in order to select a subspace of this Connection object's database in which to work. @Override public void setCatalog(String catalog) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Sets the value of the connection's client info properties. @Override public void setClientInfo(Properties properties) { // No-op (key client properties cannot be changed after the connection has been opened anyways!) } // Sets the value of the client info property specified by name to the value specified by value. @Override public void setClientInfo(String name, String value) { // No-op (key client properties cannot be changed after the connection has been opened anyways!) } // Changes the default holdability of ResultSet objects created using this Connection object to the given holdability. @Override public void setHoldability(int holdability) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Puts this connection in read-only mode as a hint to the driver to enable database optimizations. @Override public void setReadOnly(boolean readOnly) throws SQLException { checkClosed(); if (!Boolean.parseBoolean(props.getProperty("enableSetReadOnly","false"))){ throw SQLError.noSupport(); } } // Creates an unnamed savepoint in the current transaction and returns the new Savepoint object that represents it. @Override public Savepoint setSavepoint() throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Creates a savepoint with the given name in the current transaction and returns the new Savepoint object that represents it. @Override public Savepoint setSavepoint(String name) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Attempts to change the transaction isolation level for this Connection object to the one given. @Override public void setTransactionIsolation(int level) throws SQLException { checkClosed(); if (level == TRANSACTION_SERIALIZABLE) return; throw SQLError.noSupport(); } // Installs the given TypeMap object as the type map for this Connection object. @Override public void setTypeMap(Map<String,Class<?>> map) throws SQLException { checkClosed(); throw SQLError.noSupport(); } // Returns true if this either implements the interface argument or is directly or indirectly a wrapper for an object that does. @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface.isInstance(this); } // Returns an object that implements the given interface to allow access to non-standard methods, or standard methods not exposed by the proxy. @Override public <T> T unwrap(Class<T> iface) throws SQLException { try { return iface.cast(this); } catch (ClassCastException cce) { throw SQLError.get(SQLError.ILLEGAL_ARGUMENT, iface.toString()); } } /** * Gets the new version of the performance statistics for this connection only. * @return A {@link ClientStatsContext} that correctly represents the client statistics. */ @Override public ClientStatsContext createStatsContext() { return this.NativeConnection.getClientStatsContext(); } // Save statistics to a file @Override public void saveStatistics(ClientStats stats, String file) throws IOException { this.NativeConnection.saveStatistics(stats, file); } public void setSchema(String schema) throws SQLException { throw SQLError.noSupport(); } public String getSchema() throws SQLException { throw SQLError.noSupport(); } public void abort(Executor executor) throws SQLException { throw SQLError.noSupport(); } public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { throw SQLError.noSupport(); } public int getNetworkTimeout() throws SQLException { throw SQLError.noSupport(); } @Override public void writeSummaryCSV(ClientStats stats, String path) throws IOException { this.NativeConnection.writeSummaryCSV(stats, path); } }