/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.jdbc; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Savepoint; import java.sql.Statement; import java.util.Map; import java.util.Properties; import org.h2.command.CommandInterface; import org.h2.constant.ErrorCode; import org.h2.constant.SysProperties; import org.h2.engine.ConnectionInfo; import org.h2.engine.Constants; import org.h2.engine.SessionInterface; import org.h2.engine.SessionRemote; import org.h2.message.DbException; import org.h2.message.TraceObject; import org.h2.result.ResultInterface; import org.h2.util.CloseWatcher; import org.h2.util.Utils; import org.h2.value.CompareMode; import org.h2.value.Value; import org.h2.value.ValueInt; import org.h2.value.ValueNull; import org.h2.value.ValueString; //## Java 1.6 ## import java.sql.Array; import java.sql.NClob; import java.sql.Struct; import java.sql.SQLXML; import java.sql.SQLClientInfoException; //*/ /*## Java 1.7 ## import java.util.concurrent.Executor; //*/ /** * <p> * Represents a connection (session) to a database. * </p> * <p> * Thread safety: the connection is thread-safe, because access * is synchronized. However, for compatibility with other databases, a * connection should only be used in one thread at any time. * </p> */ public class JdbcConnection extends TraceObject implements Connection { private static boolean keepOpenStackTrace; private String url; private String user; // ResultSet.HOLD_CURSORS_OVER_COMMIT private int holdability = 1; private SessionInterface session; private CommandInterface commit, rollback; private CommandInterface getReadOnly, getGeneratedKeys; private CommandInterface setLockMode, getLockMode; private CommandInterface setQueryTimeout, getQueryTimeout; private int savepointId; private String catalog; private Statement executingStatement; private CompareMode compareMode = CompareMode.getInstance(null, 0); private CloseWatcher watcher; private int queryTimeoutCache = -1; /** * INTERNAL */ public JdbcConnection(String url, Properties info) throws SQLException { this(new ConnectionInfo(url, info), true); } /** * INTERNAL */ public JdbcConnection(ConnectionInfo ci, boolean useBaseDir) throws SQLException { try { if (useBaseDir) { String baseDir = SysProperties.getBaseDir(); if (baseDir != null) { ci.setBaseDir(baseDir); } } checkJavaVersion(); // this will return an embedded or server connection session = new SessionRemote(ci).connectEmbeddedOrServer(false); trace = session.getTrace(); int id = getNextId(TraceObject.CONNECTION); setTrace(trace, TraceObject.CONNECTION, id); this.user = ci.getUserName(); if (isInfoEnabled()) { trace.infoCode("Connection " + getTraceObjectName() + " = DriverManager.getConnection(" + quote(ci.getOriginalURL()) + ", " + quote(user) + ", \"\");"); } this.url = ci.getURL(); closeOld(); watcher = CloseWatcher.register(this, session, keepOpenStackTrace); } catch (Exception e) { throw logAndConvert(e); } } /** * INTERNAL */ public JdbcConnection(JdbcConnection clone) { this.session = clone.session; trace = session.getTrace(); int id = getNextId(TraceObject.CONNECTION); setTrace(trace, TraceObject.CONNECTION, id); this.user = clone.user; this.url = clone.url; this.catalog = clone.catalog; this.commit = clone.commit; this.getGeneratedKeys = clone.getGeneratedKeys; this.getLockMode = clone.getLockMode; this.getQueryTimeout = clone.getQueryTimeout; this.getReadOnly = clone.getReadOnly; this.rollback = clone.rollback; } /** * INTERNAL */ public JdbcConnection(SessionInterface session, String user, String url) { this.session = session; trace = session.getTrace(); int id = getNextId(TraceObject.CONNECTION); setTrace(trace, TraceObject.CONNECTION, id); this.user = user; this.url = url; } private void closeOld() { while (true) { CloseWatcher w = CloseWatcher.pollUnclosed(); if (w == null) { break; } try { w.getCloseable().close(); } catch (Exception e) { trace.error(e, "closing session"); } // there was an unclosed object - // keep the stack trace from now on keepOpenStackTrace = true; String s = w.getOpenStackTrace(); Exception ex = DbException.get(ErrorCode.TRACE_CONNECTION_NOT_CLOSED); trace.error(ex, s); } } /** * Creates a new statement. * * @return the new statement * @throws SQLException if the connection is closed */ public Statement createStatement() throws SQLException { try { int id = getNextId(TraceObject.STATEMENT); if (isDebugEnabled()) { debugCodeAssign("Statement", TraceObject.STATEMENT, id, "createStatement()"); } checkClosed(); return new JdbcStatement(this, id, ResultSet.TYPE_FORWARD_ONLY, Constants.DEFAULT_RESULT_SET_CONCURRENCY, false); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a statement with the specified result set type and concurrency. * * @param resultSetType the result set type (ResultSet.TYPE_*) * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) * @return the statement * @throws SQLException * if the connection is closed or the result set type or * concurrency are not supported */ public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { try { int id = getNextId(TraceObject.STATEMENT); if (isDebugEnabled()) { debugCodeAssign("Statement", TraceObject.STATEMENT, id, "createStatement(" + resultSetType + ", " + resultSetConcurrency + ")"); } checkTypeConcurrency(resultSetType, resultSetConcurrency); checkClosed(); return new JdbcStatement(this, id, resultSetType, resultSetConcurrency, false); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a statement with the specified result set type, concurrency, and * holdability. * * @param resultSetType the result set type (ResultSet.TYPE_*) * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) * @return the statement * @throws SQLException if the connection is closed or the result set type, * concurrency, or holdability are not supported */ public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { try { int id = getNextId(TraceObject.STATEMENT); if (isDebugEnabled()) { debugCodeAssign("Statement", TraceObject.STATEMENT, id, "createStatement(" + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")"); } checkTypeConcurrency(resultSetType, resultSetConcurrency); checkHoldability(resultSetHoldability); checkClosed(); return new JdbcStatement(this, id, resultSetType, resultSetConcurrency, false); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a new prepared statement. * * @param sql the SQL statement * @return the prepared statement * @throws SQLException if the connection is closed */ public PreparedStatement prepareStatement(String sql) throws SQLException { try { int id = getNextId(TraceObject.PREPARED_STATEMENT); if (isDebugEnabled()) { debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, "prepareStatement(" + quote(sql) + ")"); } checkClosed(); sql = translateSQL(sql); return new JdbcPreparedStatement(this, sql, id, ResultSet.TYPE_FORWARD_ONLY, Constants.DEFAULT_RESULT_SET_CONCURRENCY, false); } catch (Exception e) { throw logAndConvert(e); } } /** * Prepare a statement that will automatically close when the result set is * closed. This method is used to retrieve database meta data. * * @param sql the SQL statement * @return the prepared statement */ PreparedStatement prepareAutoCloseStatement(String sql) throws SQLException { try { int id = getNextId(TraceObject.PREPARED_STATEMENT); if (isDebugEnabled()) { debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, "prepareStatement(" + quote(sql) + ")"); } checkClosed(); sql = translateSQL(sql); return new JdbcPreparedStatement(this, sql, id, ResultSet.TYPE_FORWARD_ONLY, Constants.DEFAULT_RESULT_SET_CONCURRENCY, true); } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the database meta data for this database. * * @return the database meta data * @throws SQLException if the connection is closed */ public DatabaseMetaData getMetaData() throws SQLException { try { int id = getNextId(TraceObject.DATABASE_META_DATA); if (isDebugEnabled()) { debugCodeAssign("DatabaseMetaData", TraceObject.DATABASE_META_DATA, id, "getMetaData()"); } checkClosed(); return new JdbcDatabaseMetaData(this, trace, id); } catch (Exception e) { throw logAndConvert(e); } } /** * INTERNAL */ public SessionInterface getSession() { return session; } /** * Closes this connection. All open statements, prepared statements and * result sets that where created by this connection become invalid after * calling this method. If there is an uncommitted transaction, it will be * rolled back. */ public synchronized void close() throws SQLException { try { debugCodeCall("close"); if (session == null) { return; } CloseWatcher.unregister(watcher); session.cancel(); if (executingStatement != null) { try { executingStatement.cancel(); } catch (NullPointerException e) { // ignore } } synchronized (session) { try { if (!session.isClosed()) { try { if (session.getUndoLogPos() != 0) { // roll back unless that would require to re-connect // (the transaction can't be rolled back after re-connecting) if (!session.isReconnectNeeded(true)) { try { rollbackInternal(); } catch (DbException e) { // ignore if the connection is broken right now if (e.getErrorCode() != ErrorCode.CONNECTION_BROKEN_1) { throw e; } } } session.afterWriting(); } closePreparedCommands(); } finally { session.close(); } } } finally { session = null; } } } catch (Exception e) { throw logAndConvert(e); } } private void closePreparedCommands() { commit = closeAndSetNull(commit); rollback = closeAndSetNull(rollback); getReadOnly = closeAndSetNull(getReadOnly); getGeneratedKeys = closeAndSetNull(getGeneratedKeys); getLockMode = closeAndSetNull(getLockMode); setLockMode = closeAndSetNull(setLockMode); getQueryTimeout = closeAndSetNull(getQueryTimeout); setQueryTimeout = closeAndSetNull(setQueryTimeout); } private static CommandInterface closeAndSetNull(CommandInterface command) { if (command != null) { command.close(); } return null; } /** * Switches auto commit on or off. Enabling it commits an uncommitted * transaction, if there is one. * * @param autoCommit true for auto commit on, false for off * @throws SQLException if the connection is closed */ public synchronized void setAutoCommit(boolean autoCommit) throws SQLException { try { if (isDebugEnabled()) { debugCode("setAutoCommit(" + autoCommit + ");"); } checkClosed(); if (autoCommit && !session.getAutoCommit()) { commit(); } session.setAutoCommit(autoCommit); } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the current setting for auto commit. * * @return true for on, false for off * @throws SQLException if the connection is closed */ public synchronized boolean getAutoCommit() throws SQLException { try { checkClosed(); debugCodeCall("getAutoCommit"); return session.getAutoCommit(); } catch (Exception e) { throw logAndConvert(e); } } /** * Commits the current transaction. This call has only an effect if auto * commit is switched off. * * @throws SQLException if the connection is closed */ public synchronized void commit() throws SQLException { try { debugCodeCall("commit"); checkClosedForWrite(); try { commit = prepareCommand("COMMIT", commit); commit.executeUpdate(); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Rolls back the current transaction. This call has only an effect if auto * commit is switched off. * * @throws SQLException if the connection is closed */ public synchronized void rollback() throws SQLException { try { debugCodeCall("rollback"); checkClosedForWrite(); try { rollbackInternal(); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Returns true if this connection has been closed. * * @return true if close was called */ public boolean isClosed() throws SQLException { try { debugCodeCall("isClosed"); return session == null || session.isClosed(); } catch (Exception e) { throw logAndConvert(e); } } /** * Translates a SQL statement into the database grammar. * * @param sql the SQL statement with or without JDBC escape sequences * @return the translated statement * @throws SQLException if the connection is closed */ public String nativeSQL(String sql) throws SQLException { try { debugCodeCall("nativeSQL", sql); checkClosed(); return translateSQL(sql); } catch (Exception e) { throw logAndConvert(e); } } /** * According to the JDBC specs, this setting is only a hint to the database * to enable optimizations - it does not cause writes to be prohibited. * * @param readOnly ignored * @throws SQLException if the connection is closed */ public void setReadOnly(boolean readOnly) throws SQLException { try { if (isDebugEnabled()) { debugCode("setReadOnly(" + readOnly + ");"); } checkClosed(); } catch (Exception e) { throw logAndConvert(e); } } /** * Returns true if the database is read-only. * * @return if the database is read-only * @throws SQLException if the connection is closed */ public boolean isReadOnly() throws SQLException { try { debugCodeCall("isReadOnly"); checkClosed(); getReadOnly = prepareCommand("CALL READONLY()", getReadOnly); ResultInterface result = getReadOnly.executeQuery(0, false); result.next(); boolean readOnly = result.currentRow()[0].getBoolean().booleanValue(); return readOnly; } catch (Exception e) { throw logAndConvert(e); } } /** * Set the default catalog name. This call is ignored. * * @param catalog ignored * @throws SQLException if the connection is closed */ public void setCatalog(String catalog) throws SQLException { try { debugCodeCall("setCatalog", catalog); checkClosed(); } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the current catalog name. * * @return the catalog name * @throws SQLException if the connection is closed */ public String getCatalog() throws SQLException { try { debugCodeCall("getCatalog"); checkClosed(); if (catalog == null) { CommandInterface cat = prepareCommand("CALL DATABASE()", Integer.MAX_VALUE); ResultInterface result = cat.executeQuery(0, false); result.next(); catalog = result.currentRow()[0].getString(); cat.close(); } return catalog; } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the first warning reported by calls on this object. * * @return null */ public SQLWarning getWarnings() throws SQLException { try { debugCodeCall("getWarnings"); checkClosed(); return null; } catch (Exception e) { throw logAndConvert(e); } } /** * Clears all warnings. */ public void clearWarnings() throws SQLException { try { debugCodeCall("clearWarnings"); checkClosed(); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a prepared statement with the specified result set type and * concurrency. * * @param sql the SQL statement * @param resultSetType the result set type (ResultSet.TYPE_*) * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) * @return the prepared statement * @throws SQLException * if the connection is closed or the result set type or * concurrency are not supported */ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { try { int id = getNextId(TraceObject.PREPARED_STATEMENT); if (isDebugEnabled()) { debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, "prepareStatement(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ")"); } checkTypeConcurrency(resultSetType, resultSetConcurrency); checkClosed(); sql = translateSQL(sql); return new JdbcPreparedStatement(this, sql, id, resultSetType, resultSetConcurrency, false); } catch (Exception e) { throw logAndConvert(e); } } /** * Changes the current transaction isolation level. Calling this method will * commit an open transaction, even if the new level is the same as the old * one, except if the level is not supported. Internally, this method calls * SET LOCK_MODE. The following isolation levels are supported: * <ul> * <li> Connection.TRANSACTION_READ_UNCOMMITTED = SET LOCK_MODE 0: no * locking (should only be used for testing). </li> * <li>Connection.TRANSACTION_SERIALIZABLE = SET LOCK_MODE 1: table level * locking. </li> * <li>Connection.TRANSACTION_READ_COMMITTED = SET LOCK_MODE 3: table * level locking, but read locks are released immediately (default). </li> * </ul> * This setting is not persistent. Please note that using * TRANSACTION_READ_UNCOMMITTED while at the same time using multiple * connections may result in inconsistent transactions. * * @param level the new transaction isolation level: * Connection.TRANSACTION_READ_UNCOMMITTED, * Connection.TRANSACTION_READ_COMMITTED, or * Connection.TRANSACTION_SERIALIZABLE * @throws SQLException if the connection is closed or the isolation level * is not supported */ public void setTransactionIsolation(int level) throws SQLException { try { debugCodeCall("setTransactionIsolation", level); checkClosed(); int lockMode; switch(level) { case Connection.TRANSACTION_READ_UNCOMMITTED: lockMode = Constants.LOCK_MODE_OFF; break; case Connection.TRANSACTION_READ_COMMITTED: lockMode = Constants.LOCK_MODE_READ_COMMITTED; break; case Connection.TRANSACTION_REPEATABLE_READ: case Connection.TRANSACTION_SERIALIZABLE: lockMode = Constants.LOCK_MODE_TABLE; break; default: throw DbException.getInvalidValueException("level", level); } commit(); setLockMode = prepareCommand("SET LOCK_MODE ?", setLockMode); setLockMode.getParameters().get(0).setValue(ValueInt.get(lockMode), false); setLockMode.executeUpdate(); } catch (Exception e) { throw logAndConvert(e); } } /** * INTERNAL */ public void setQueryTimeout(int seconds) throws SQLException { try { debugCodeCall("setQueryTimeout", seconds); checkClosed(); setQueryTimeout = prepareCommand("SET QUERY_TIMEOUT ?", setQueryTimeout); setQueryTimeout.getParameters().get(0).setValue(ValueInt.get(seconds * 1000), false); setQueryTimeout.executeUpdate(); queryTimeoutCache = seconds; } catch (Exception e) { throw logAndConvert(e); } } /** * INTERNAL */ public int getQueryTimeout() throws SQLException { try { debugCodeCall("getQueryTimeout"); if (queryTimeoutCache == -1) { checkClosed(); getQueryTimeout = prepareCommand("SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME=?", getQueryTimeout); getQueryTimeout.getParameters().get(0).setValue(ValueString.get("QUERY_TIMEOUT"), false); ResultInterface result = getQueryTimeout.executeQuery(0, false); result.next(); int queryTimeout = result.currentRow()[0].getInt(); result.close(); if (queryTimeout != 0) { // round to the next second, otherwise 999 millis would return 0 seconds queryTimeout = (queryTimeout + 999) / 1000; } queryTimeoutCache = queryTimeout; } return queryTimeoutCache; } catch (Exception e) { throw logAndConvert(e); } } /** * Returns the current transaction isolation level. * * @return the isolation level. * @throws SQLException if the connection is closed */ public int getTransactionIsolation() throws SQLException { try { debugCodeCall("getTransactionIsolation"); checkClosed(); getLockMode = prepareCommand("CALL LOCK_MODE()", getLockMode); ResultInterface result = getLockMode.executeQuery(0, false); result.next(); int lockMode = result.currentRow()[0].getInt(); result.close(); int transactionIsolationLevel; switch(lockMode) { case Constants.LOCK_MODE_OFF: transactionIsolationLevel = Connection.TRANSACTION_READ_UNCOMMITTED; break; case Constants.LOCK_MODE_READ_COMMITTED: transactionIsolationLevel = Connection.TRANSACTION_READ_COMMITTED; break; case Constants.LOCK_MODE_TABLE: case Constants.LOCK_MODE_TABLE_GC: transactionIsolationLevel = Connection.TRANSACTION_SERIALIZABLE; break; default: throw DbException.throwInternalError("lockMode:" + lockMode); } return transactionIsolationLevel; } catch (Exception e) { throw logAndConvert(e); } } /** * Changes the current result set holdability. * * @param holdability * ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT; * @throws SQLException * if the connection is closed or the holdability is not * supported */ public void setHoldability(int holdability) throws SQLException { try { debugCodeCall("setHoldability", holdability); checkClosed(); checkHoldability(holdability); this.holdability = holdability; } catch (Exception e) { throw logAndConvert(e); } } /** * Returns the current result set holdability. * * @return the holdability * @throws SQLException if the connection is closed */ public int getHoldability() throws SQLException { try { debugCodeCall("getHoldability"); checkClosed(); return holdability; } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the type map. * * @return null * @throws SQLException if the connection is closed */ public Map<String, Class<?>> getTypeMap() throws SQLException { try { debugCodeCall("getTypeMap"); checkClosed(); return null; } catch (Exception e) { throw logAndConvert(e); } } /** * [Partially supported] Sets the type map. This is only supported if the * map is empty or null. */ public void setTypeMap(Map<String, Class<?>> map) throws SQLException { try { debugCode("setTypeMap(" + quoteMap(map) + ");"); checkMap(map); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a new callable statement. * * @param sql the SQL statement * @return the callable statement * @throws SQLException * if the connection is closed or the statement is not valid */ public CallableStatement prepareCall(String sql) throws SQLException { try { int id = getNextId(TraceObject.CALLABLE_STATEMENT); if (isDebugEnabled()) { debugCodeAssign("CallableStatement", TraceObject.CALLABLE_STATEMENT, id, "prepareCall(" + quote(sql) + ")"); } checkClosed(); sql = translateSQL(sql); return new JdbcCallableStatement(this, sql, id, ResultSet.TYPE_FORWARD_ONLY, Constants.DEFAULT_RESULT_SET_CONCURRENCY); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a callable statement with the specified result set type and * concurrency. * * @param sql the SQL statement * @param resultSetType the result set type (ResultSet.TYPE_*) * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) * @return the callable statement * @throws SQLException * if the connection is closed or the result set type or * concurrency are not supported */ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { try { int id = getNextId(TraceObject.CALLABLE_STATEMENT); if (isDebugEnabled()) { debugCodeAssign("CallableStatement", TraceObject.CALLABLE_STATEMENT, id, "prepareCall(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ")"); } checkTypeConcurrency(resultSetType, resultSetConcurrency); checkClosed(); sql = translateSQL(sql); return new JdbcCallableStatement(this, sql, id, resultSetType, resultSetConcurrency); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a callable statement with the specified result set type, * concurrency, and holdability. * * @param sql the SQL statement * @param resultSetType the result set type (ResultSet.TYPE_*) * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) * @return the callable statement * @throws SQLException * if the connection is closed or the result set type, * concurrency, or holdability are not supported */ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { try { int id = getNextId(TraceObject.CALLABLE_STATEMENT); if (isDebugEnabled()) { debugCodeAssign("CallableStatement", TraceObject.CALLABLE_STATEMENT, id, "prepareCall(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")"); } checkTypeConcurrency(resultSetType, resultSetConcurrency); checkHoldability(resultSetHoldability); checkClosed(); sql = translateSQL(sql); return new JdbcCallableStatement(this, sql, id, resultSetType, resultSetConcurrency); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a new unnamed savepoint. * * @return the new savepoint */ public Savepoint setSavepoint() throws SQLException { try { int id = getNextId(TraceObject.SAVEPOINT); if (isDebugEnabled()) { debugCodeAssign("Savepoint", TraceObject.SAVEPOINT, id, "setSavepoint()"); } checkClosed(); CommandInterface set = prepareCommand("SAVEPOINT " + JdbcSavepoint.getName(null, savepointId), Integer.MAX_VALUE); set.executeUpdate(); JdbcSavepoint savepoint = new JdbcSavepoint(this, savepointId, null, trace, id); savepointId++; return savepoint; } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a new named savepoint. * * @param name the savepoint name * @return the new savepoint */ public Savepoint setSavepoint(String name) throws SQLException { try { int id = getNextId(TraceObject.SAVEPOINT); if (isDebugEnabled()) { debugCodeAssign("Savepoint", TraceObject.SAVEPOINT, id, "setSavepoint(" + quote(name) + ")"); } checkClosed(); CommandInterface set = prepareCommand("SAVEPOINT " + JdbcSavepoint.getName(name, 0), Integer.MAX_VALUE); set.executeUpdate(); JdbcSavepoint savepoint = new JdbcSavepoint(this, 0, name, trace, id); return savepoint; } catch (Exception e) { throw logAndConvert(e); } } /** * Rolls back to a savepoint. * * @param savepoint the savepoint */ public void rollback(Savepoint savepoint) throws SQLException { try { JdbcSavepoint sp = convertSavepoint(savepoint); debugCode("rollback(" + sp.getTraceObjectName() + ");"); checkClosedForWrite(); try { sp.rollback(); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Releases a savepoint. * * @param savepoint the savepoint to release */ public void releaseSavepoint(Savepoint savepoint) throws SQLException { try { debugCode("releaseSavepoint(savepoint);"); checkClosed(); convertSavepoint(savepoint).release(); } catch (Exception e) { throw logAndConvert(e); } } private static JdbcSavepoint convertSavepoint(Savepoint savepoint) { if (!(savepoint instanceof JdbcSavepoint)) { throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, "" + savepoint); } return (JdbcSavepoint) savepoint; } /** * Creates a prepared statement with the specified result set type, * concurrency, and holdability. * * @param sql the SQL statement * @param resultSetType the result set type (ResultSet.TYPE_*) * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) * @return the prepared statement * @throws SQLException if the connection is closed or the result set type, * concurrency, or holdability are not supported */ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { try { int id = getNextId(TraceObject.PREPARED_STATEMENT); if (isDebugEnabled()) { debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, "prepareStatement(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ", " + resultSetHoldability + ")"); } checkTypeConcurrency(resultSetType, resultSetConcurrency); checkHoldability(resultSetHoldability); checkClosed(); sql = translateSQL(sql); return new JdbcPreparedStatement(this, sql, id, resultSetType, resultSetConcurrency, false); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a new prepared statement. * This method just calls prepareStatement(String sql) internally. * The method getGeneratedKeys only supports one column. * * @param sql the SQL statement * @param autoGeneratedKeys ignored * @return the prepared statement * @throws SQLException * if the connection is closed */ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { try { if (isDebugEnabled()) { debugCode("prepareStatement(" + quote(sql) + ", " + autoGeneratedKeys + ");"); } return prepareStatement(sql); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a new prepared statement. * This method just calls prepareStatement(String sql) internally. * The method getGeneratedKeys only supports one column. * * @param sql the SQL statement * @param columnIndexes ignored * @return the prepared statement * @throws SQLException * if the connection is closed */ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { try { if (isDebugEnabled()) { debugCode("prepareStatement(" + quote(sql) + ", " + quoteIntArray(columnIndexes) + ");"); } return prepareStatement(sql); } catch (Exception e) { throw logAndConvert(e); } } /** * Creates a new prepared statement. * This method just calls prepareStatement(String sql) internally. * The method getGeneratedKeys only supports one column. * * @param sql the SQL statement * @param columnNames ignored * @return the prepared statement * @throws SQLException * if the connection is closed */ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { try { if (isDebugEnabled()) { debugCode("prepareStatement(" + quote(sql) + ", " + quoteArray(columnNames) + ");"); } return prepareStatement(sql); } catch (Exception e) { throw logAndConvert(e); } } // ============================================================= private static void checkJavaVersion() { try { // check for existence of this class (avoiding Class . forName) Class<?> clazz = java.sql.Savepoint.class; clazz.getClass(); } catch (NoClassDefFoundError e) { throw DbException.get(ErrorCode.UNSUPPORTED_JAVA_VERSION); } } /** * Prepare an command. This will parse the SQL statement. * * @param sql the SQL statement * @param fetchSize the fetch size (used in remote connections) * @return the command */ CommandInterface prepareCommand(String sql, int fetchSize) { return session.prepareCommand(sql, fetchSize); } private CommandInterface prepareCommand(String sql, CommandInterface old) { return old == null ? session.prepareCommand(sql, Integer.MAX_VALUE) : old; } private static int translateGetEnd(String sql, int i, char c) { int len = sql.length(); switch(c) { case '$': { if (i < len - 1 && sql.charAt(i + 1) == '$' && (i == 0 || sql.charAt(i - 1) <= ' ')) { int j = sql.indexOf("$$", i + 2); if (j < 0) { throw DbException.getSyntaxError(sql, i); } return j + 1; } return i; } case '\'': { int j = sql.indexOf('\'', i + 1); if (j < 0) { throw DbException.getSyntaxError(sql, i); } return j; } case '"': { int j = sql.indexOf('"', i + 1); if (j < 0) { throw DbException.getSyntaxError(sql, i); } return j; } case '/': { checkRunOver(i+1, len, sql); if (sql.charAt(i + 1) == '*') { // block comment int j = sql.indexOf("*/", i + 2); if (j < 0) { throw DbException.getSyntaxError(sql, i); } i = j + 1; } else if (sql.charAt(i + 1) == '/') { // single line comment i += 2; while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') { i++; } } return i; } case '-': { checkRunOver(i+1, len, sql); if (sql.charAt(i + 1) == '-') { // single line comment i += 2; while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') { i++; } } return i; } default: throw DbException.throwInternalError("c=" + c); } } /** * Convert JDBC escape sequences in the SQL statement. This * method throws an exception if the SQL statement is null. * * @param sql the SQL statement with or without JDBC escape sequences * @return the SQL statement without JDBC escape sequences */ private static String translateSQL(String sql) { return translateSQL(sql, true); } /** * Convert JDBC escape sequences in the SQL statement if required. This * method throws an exception if the SQL statement is null. * * @param sql the SQL statement with or without JDBC escape sequences * @param escapeProcessing whether escape sequences should be replaced * @return the SQL statement without JDBC escape sequences */ static String translateSQL(String sql, boolean escapeProcessing) { if (sql == null) { throw DbException.getInvalidValueException("SQL", null); } if (!escapeProcessing) { return sql; } if (sql.indexOf('{') < 0) { return sql; } int len = sql.length(); char[] chars = null; int level = 0; for (int i = 0; i < len; i++) { char c = sql.charAt(i); switch (c) { case '\'': case '"': case '/': case '-': i = translateGetEnd(sql, i, c); break; case '{': level++; if (chars == null) { chars = sql.toCharArray(); } chars[i] = ' '; while (Character.isSpaceChar(chars[i])) { i++; checkRunOver(i, len, sql); } int start = i; if (chars[i] >= '0' && chars[i] <= '9') { chars[i - 1] = '{'; while (true) { checkRunOver(i, len, sql); c = chars[i]; if (c == '}') { break; } switch (c) { case '\'': case '"': case '/': case '-': i = translateGetEnd(sql, i, c); break; default: } i++; } level--; break; } else if (chars[i] == '?') { i++; checkRunOver(i, len, sql); while (Character.isSpaceChar(chars[i])) { i++; checkRunOver(i, len, sql); } if (sql.charAt(i) != '=') { throw DbException.getSyntaxError(sql, i, "="); } i++; checkRunOver(i, len, sql); while (Character.isSpaceChar(chars[i])) { i++; checkRunOver(i, len, sql); } } while (!Character.isSpaceChar(chars[i])) { i++; checkRunOver(i, len, sql); } int remove = 0; if (found(sql, start, "fn")) { remove = 2; } else if (found(sql, start, "escape")) { break; } else if (found(sql, start, "call")) { break; } else if (found(sql, start, "oj")) { remove = 2; } else if (found(sql, start, "ts")) { remove = 2; } else if (found(sql, start, "t")) { remove = 1; } else if (found(sql, start, "d")) { remove = 1; } else if (found(sql, start, "params")) { remove = "params".length(); } for (i = start; remove > 0; i++, remove--) { chars[i] = ' '; } break; case '}': if (--level < 0) { throw DbException.getSyntaxError(sql, i); } chars[i] = ' '; break; case '$': i = translateGetEnd(sql, i, c); break; default: } } if (level != 0) { throw DbException.getSyntaxError(sql, sql.length() - 1); } if (chars != null) { sql = new String(chars); } return sql; } private static void checkRunOver(int i, int len, String sql) { if (i >= len) { throw DbException.getSyntaxError(sql, i); } } private static boolean found(String sql, int start, String other) { return sql.regionMatches(true, start, other, 0, other.length()); } private static void checkTypeConcurrency(int resultSetType, int resultSetConcurrency) { switch (resultSetType) { case ResultSet.TYPE_FORWARD_ONLY: case ResultSet.TYPE_SCROLL_INSENSITIVE: case ResultSet.TYPE_SCROLL_SENSITIVE: break; default: throw DbException.getInvalidValueException("resultSetType", resultSetType); } switch (resultSetConcurrency) { case ResultSet.CONCUR_READ_ONLY: case ResultSet.CONCUR_UPDATABLE: break; default: throw DbException.getInvalidValueException("resultSetConcurrency", resultSetConcurrency); } } private static void checkHoldability(int resultSetHoldability) { // TODO compatibility / correctness: DBPool uses // ResultSet.HOLD_CURSORS_OVER_COMMIT if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT && resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) { throw DbException.getInvalidValueException("resultSetHoldability", resultSetHoldability); } } /** * INTERNAL. * Check if this connection is closed. * The next operation is a read request. * * @throws DbException if the connection or session is closed */ protected void checkClosed() { checkClosed(false); } /** * Check if this connection is closed. * The next operation may be a write request. * * @throws DbException if the connection or session is closed */ private void checkClosedForWrite() { checkClosed(true); } /** * INTERNAL. * Check if this connection is closed. * * @param write if the next operation is possibly writing * @throws DbException if the connection or session is closed */ protected void checkClosed(boolean write) { if (session == null) { throw DbException.get(ErrorCode.OBJECT_CLOSED); } if (session.isClosed()) { throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); } if (session.isReconnectNeeded(write)) { trace.debug("reconnect"); closePreparedCommands(); session = session.reconnect(write); trace = session.getTrace(); } } /** * INTERNAL. * Called after executing a command that could have written something. */ protected void afterWriting() { if (session != null) { session.afterWriting(); } } String getURL() { checkClosed(); return url; } String getUser() { checkClosed(); return user; } private void rollbackInternal() { rollback = prepareCommand("ROLLBACK", rollback); rollback.executeUpdate(); } /** * INTERNAL */ public int getPowerOffCount() { return (session == null || session.isClosed()) ? 0 : session.getPowerOffCount(); } /** * INTERNAL */ public void setPowerOffCount(int count) { if (session != null) { session.setPowerOffCount(count); } } /** * INTERNAL */ public void setExecutingStatement(Statement stat) { executingStatement = stat; } /** * INTERNAL */ ResultSet getGeneratedKeys(JdbcStatement stat, int id) { getGeneratedKeys = prepareCommand("SELECT SCOPE_IDENTITY() WHERE SCOPE_IDENTITY() IS NOT NULL", getGeneratedKeys); ResultInterface result = getGeneratedKeys.executeQuery(0, false); ResultSet rs = new JdbcResultSet(this, stat, result, id, false, true, false); return rs; } /** * Create a new empty Clob object. * * @return the object */ public Clob createClob() throws SQLException { try { int id = getNextId(TraceObject.CLOB); debugCodeAssign("Clob", TraceObject.CLOB, id, "createClob()"); checkClosedForWrite(); try { Value v = session.getDataHandler().getLobStorage().createClob( new InputStreamReader( new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0); return new JdbcClob(this, v, id); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Create a new empty Blob object. * * @return the object */ public Blob createBlob() throws SQLException { try { int id = getNextId(TraceObject.BLOB); debugCodeAssign("Blob", TraceObject.BLOB, id, "createClob()"); checkClosedForWrite(); try { Value v = session.getDataHandler().getLobStorage().createBlob( new ByteArrayInputStream(Utils.EMPTY_BYTES), 0); return new JdbcBlob(this, v, id); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Create a new empty NClob object. * * @return the object */ //## Java 1.6 ## public NClob createNClob() throws SQLException { try { int id = getNextId(TraceObject.CLOB); debugCodeAssign("NClob", TraceObject.CLOB, id, "createNClob()"); checkClosedForWrite(); try { Value v = session.getDataHandler().getLobStorage().createClob( new InputStreamReader( new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0); return new JdbcClob(this, v, id); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } //*/ /** * [Not supported] Create a new empty SQLXML object. */ //## Java 1.6 ## public SQLXML createSQLXML() throws SQLException { throw unsupported("SQLXML"); } //*/ /** * [Not supported] Create a new empty Array object. */ //## Java 1.6 ## public Array createArrayOf(String typeName, Object[] elements) throws SQLException { throw unsupported("createArray"); } //*/ /** * [Not supported] Create a new empty Struct object. */ //## Java 1.6 ## public Struct createStruct(String typeName, Object[] attributes) throws SQLException { throw unsupported("Struct"); } //*/ /** * Returns true if this connection is still valid. * * @param timeout the number of seconds to wait for the database to respond * (ignored) * @return true if the connection is valid. */ public synchronized boolean isValid(int timeout) { try { debugCodeCall("isValid", timeout); if (session == null || session.isClosed()) { return false; } // force a network round trip (if networked) getTransactionIsolation(); return true; } catch (Exception e) { // this method doesn't throw an exception, but it logs it logAndConvert(e); return false; } } /** * [Not supported] Set a client property. */ //## Java 1.6 ## public void setClientInfo(String name, String value) throws SQLClientInfoException { throw new SQLClientInfoException(); } //*/ /** * [Not supported] Set the client properties. */ //## Java 1.6 ## public void setClientInfo(Properties properties) throws SQLClientInfoException { throw new SQLClientInfoException(); } //*/ /** * [Not supported] Get the client properties. */ //## Java 1.6 ## public Properties getClientInfo() throws SQLClientInfoException { throw new SQLClientInfoException(); } //*/ /** * [Not supported] Set a client property. */ //## Java 1.6 ## public String getClientInfo(String name) throws SQLException { throw unsupported("clientInfo"); } //*/ /** * [Not supported] Return an object of this class if possible. * * @param iface the class */ //## Java 1.6 ## public <T> T unwrap(Class<T> iface) throws SQLException { throw unsupported("unwrap"); } //*/ /** * [Not supported] Checks if unwrap can return an object of this class. * * @param iface the class */ //## Java 1.6 ## public boolean isWrapperFor(Class<?> iface) throws SQLException { throw unsupported("isWrapperFor"); } //*/ /** * Create a Clob value from this reader. * * @param x the reader * @param length the length (if smaller or equal than 0, all data until the * end of file is read) * @return the value */ public Value createClob(Reader x, long length) { if (x == null) { return ValueNull.INSTANCE; } if (length <= 0) { length = -1; } Value v = session.getDataHandler().getLobStorage().createClob(x, length); return v; } /** * Create a Blob value from this input stream. * * @param x the input stream * @param length the length (if smaller or equal than 0, all data until the * end of file is read) * @return the value */ public Value createBlob(InputStream x, long length) { if (x == null) { return ValueNull.INSTANCE; } if (length <= 0) { length = -1; } Value v = session.getDataHandler().getLobStorage().createBlob(x, length); return v; } /** * [Not supported] * * @param schema the schema */ /*## Java 1.7 ## public void setSchema(String schema) { // not supported } //*/ /** * [Not supported] */ /*## Java 1.7 ## public String getSchema() { return null; } //*/ /** * [Not supported] * * @param executor the executor used by this method */ /*## Java 1.7 ## public void abort(Executor executor) { // not supported } //*/ /** * [Not supported] * * @param executor the executor used by this method * @param milliseconds the TCP connection timeout */ /*## Java 1.7 ## public void setNetworkTimeout(Executor executor, int milliseconds) { // not supported } //*/ /** * [Not supported] */ /*## Java 1.7 ## public int getNetworkTimeout() { return 0; } //*/ private static void checkMap(Map<String, Class<?>> map) { if (map != null && map.size() > 0) { throw DbException.getUnsupportedException("map.size > 0"); } } /** * INTERNAL */ public String toString() { return getTraceObjectName() + ": url=" + url + " user=" + user; } /** * Convert an object to the default Java object for the given SQL type. For * example, LOB objects are converted to java.sql.Clob / java.sql.Blob. * * @param v the value * @return the object */ Object convertToDefaultObject(Value v) { Object o; switch (v.getType()) { case Value.CLOB: { int id = getNextId(TraceObject.CLOB); o = new JdbcClob(this, v, id); break; } case Value.BLOB: { int id = getNextId(TraceObject.BLOB); o = new JdbcBlob(this, v, id); break; } case Value.JAVA_OBJECT: if (SysProperties.serializeJavaObject) { o = Utils.deserialize(v.getBytesNoCopy()); break; } default: o = v.getObject(); } return o; } CompareMode getCompareMode() { return compareMode; } /** * INTERNAL */ public void setTraceLevel(int level) { trace.setLevel(level); } }