/* * Copyright 2014-2015 the original author or authors * * Licensed under the Apache License, Version 2.0 (the “License”); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an “AS IS” BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.wplatform.ddal.jdbc; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLClientInfoException; 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 com.wplatform.ddal.command.CommandInterface; import com.wplatform.ddal.engine.Constants; import com.wplatform.ddal.engine.SessionInterface; import com.wplatform.ddal.engine.SysProperties; import com.wplatform.ddal.message.DbException; import com.wplatform.ddal.message.ErrorCode; import com.wplatform.ddal.message.TraceObject; import com.wplatform.ddal.result.ResultInterface; import com.wplatform.ddal.util.JdbcUtils; import com.wplatform.ddal.util.Utils; import com.wplatform.ddal.value.CompareMode; import com.wplatform.ddal.value.Value; import com.wplatform.ddal.value.ValueInt; import com.wplatform.ddal.value.ValueLobDb; import com.wplatform.ddal.value.ValueNull; import com.wplatform.ddal.value.ValueString; /** * <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 final String url; private final String user; private final CompareMode compareMode = CompareMode.getInstance(null, 0); // ResultSet.HOLD_CURSORS_OVER_COMMIT private int holdability = 1; private SessionInterface session; private CommandInterface readOnlyTrue, readOnlyFalse; private CommandInterface autoCommitTrue, autoCommitFalse; private CommandInterface setIsolation; private CommandInterface commit, rollback; private CommandInterface getGeneratedKeys; private CommandInterface setQueryTimeout, getQueryTimeout; private boolean autoCommit = true; private boolean readOnly; private int transactionIsolation; private int savepointId; private String catalog; private Statement executingStatement; private int queryTimeoutCache = -1; /** * INTERNAL */ public JdbcConnection(String url, Properties info) throws SQLException { throw new SQLException("client mode unsupported."); } /** * 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.getQueryTimeout = clone.getQueryTimeout; 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 static CommandInterface closeAndSetNull(CommandInterface command) { if (command != null) { command.close(); } return null; } private static JdbcSavepoint convertSavepoint(Savepoint savepoint) { if (!(savepoint instanceof JdbcSavepoint)) { throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, "" + savepoint); } return (JdbcSavepoint) savepoint; } 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")) { break; } else if (found(sql, start, "t")) { break; } else if (found(sql, start, "d")) { break; } 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); } } private static SQLClientInfoException convertToClientInfoException(SQLException x) { if (x instanceof SQLClientInfoException) { return (SQLClientInfoException) x; } return new SQLClientInfoException(x.getMessage(), x.getSQLState(), x.getErrorCode(), null, null); } /** * Check that the given type map is either null or empty. * * @param map the type map * @throws DbException if the map is not empty */ static void checkMap(Map<String, Class<?>> map) { if (map != null && map.size() > 0) { throw DbException.getUnsupportedException("map.size > 0"); } } /** * Creates a new statement. * * @return the new statement * @throws SQLException if the connection is closed */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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. */ @Override public synchronized void close() throws SQLException { try { debugCodeCall("close"); if (session == null) { return; } session.cancel(); if (executingStatement != null) { try { executingStatement.cancel(); } catch (NullPointerException e) { // ignore } } synchronized (session) { try { if (!session.isClosed()) { try { if (session.hasPendingTransaction()) { // 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; } } } } closePreparedCommands(); } finally { session.close(); } } } finally { session = null; } } } catch (Exception e) { throw logAndConvert(e); } } private void closePreparedCommands() { commit = closeAndSetNull(commit); rollback = closeAndSetNull(rollback); autoCommitTrue = closeAndSetNull(autoCommitTrue); autoCommitFalse = closeAndSetNull(autoCommitFalse); readOnlyFalse = closeAndSetNull(readOnlyFalse); readOnlyTrue = closeAndSetNull(readOnlyTrue); setIsolation = closeAndSetNull(setIsolation); getGeneratedKeys = closeAndSetNull(getGeneratedKeys); getQueryTimeout = closeAndSetNull(getQueryTimeout); setQueryTimeout = closeAndSetNull(setQueryTimeout); } /** * Gets the current setting for auto commit. * * @return true for on, false for off * @throws SQLException if the connection is closed */ @Override public synchronized boolean getAutoCommit() throws SQLException { try { checkClosed(); debugCodeCall("getAutoCommit"); return autoCommit; } catch (Exception e) { throw logAndConvert(e); } } /** * 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 */ @Override public synchronized void setAutoCommit(boolean autoCommit) throws SQLException { try { if (isDebugEnabled()) { debugCode("setAutoCommit(" + autoCommit + ");"); } checkClosed(); if (readOnly) { autoCommitTrue = prepareCommand("SET AUTOCOMMIT TRUE", autoCommitTrue); autoCommitTrue.executeUpdate(); } else { autoCommitFalse = prepareCommand("SET AUTOCOMMIT FALSE", autoCommitFalse); autoCommitFalse.executeUpdate(); } this.autoCommit = autoCommit; } 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override public String nativeSQL(String sql) throws SQLException { try { debugCodeCall("nativeSQL", sql); checkClosed(); return translateSQL(sql); } 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 */ @Override public boolean isReadOnly() throws SQLException { try { if (isDebugEnabled()) { debugCode("isReadOnly"); } checkClosed(); return readOnly; } 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 */ @Override public void setReadOnly(boolean readOnly) throws SQLException { try { if (isDebugEnabled()) { debugCode("setReadOnly(" + readOnly + ");"); } checkClosed(); if (readOnly) { readOnlyTrue = prepareCommand("SET TRANSACTION READ ONLY", readOnlyTrue); readOnlyTrue.executeUpdate(); } else { readOnlyFalse = prepareCommand("SET TRANSACTION READ WRITE", readOnlyFalse); readOnlyFalse.executeUpdate(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the current catalog name. * * @return the catalog name * @throws SQLException if the connection is closed */ @Override 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); } } /** * Set the default catalog name. This call is ignored. * * @param catalog ignored * @throws SQLException if the connection is closed */ @Override public void setCatalog(String catalog) throws SQLException { try { debugCodeCall("setCatalog", catalog); checkClosed(); } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the first warning reported by calls on this object. * * @return null */ @Override public SQLWarning getWarnings() throws SQLException { try { debugCodeCall("getWarnings"); checkClosed(); return null; } catch (Exception e) { throw logAndConvert(e); } } /** * Clears all warnings. */ @Override 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 */ @Override 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); } } /** * INTERNAL */ int getQueryTimeout() throws SQLException { try { 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); } } /** * 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); } } /** * Returns the current transaction isolation level. * * @return the isolation level. * @throws SQLException if the connection is closed */ @Override public int getTransactionIsolation() throws SQLException { try { if (isDebugEnabled()) { debugCode("getTransactionIsolation"); } checkClosed(); return transactionIsolation; } 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, which affects all connections. 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 */ @Override public void setTransactionIsolation(int level) throws SQLException { try { if (isDebugEnabled()) { debugCode("setTransactionIsolation( " + level + " )"); } checkClosed(); switch (level) { case Connection.TRANSACTION_READ_UNCOMMITTED: case Connection.TRANSACTION_READ_COMMITTED: case Connection.TRANSACTION_REPEATABLE_READ: case Connection.TRANSACTION_SERIALIZABLE: break; default: throw DbException.getInvalidValueException("transaction isolation", level); } setIsolation = prepareCommand("SET TRANSACTION ISOLATION LEVEL ?", setIsolation); setIsolation.getParameters().get(0).setValue(ValueInt.get(level), false); setIsolation.executeUpdate(); transactionIsolation = level; } catch (Exception e) { throw logAndConvert(e); } } /** * Returns the current result set holdability. * * @return the holdability * @throws SQLException if the connection is closed */ @Override public int getHoldability() throws SQLException { try { debugCodeCall("getHoldability"); checkClosed(); return holdability; } 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 */ @Override public void setHoldability(int holdability) throws SQLException { try { debugCodeCall("setHoldability", holdability); checkClosed(); checkHoldability(holdability); this.holdability = holdability; } catch (Exception e) { throw logAndConvert(e); } } /** * Gets the type map. * * @return null * @throws SQLException if the connection is closed */ @Override 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. */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { try { debugCode("releaseSavepoint(savepoint);"); checkClosed(); convertSavepoint(savepoint).release(); } catch (Exception e) { throw logAndConvert(e); } } /** * 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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); } } /** * 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; } /** * 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) { } } String getURL() { checkClosed(); return url; } String getUser() { checkClosed(); return user; } private void rollbackInternal() { rollback = prepareCommand("ROLLBACK", rollback); rollback.executeUpdate(); } /** * 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 */ @Override public Clob createClob() throws SQLException { try { int id = getNextId(TraceObject.CLOB); debugCodeAssign("Clob", TraceObject.CLOB, id, "createClob()"); checkClosedForWrite(); try { Value v = ValueLobDb.createTempClob( new InputStreamReader( new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0); session.addTemporaryLob(v); return new JdbcClob(this, v, id); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Create a new empty Blob object. * * @return the object */ @Override public Blob createBlob() throws SQLException { try { int id = getNextId(TraceObject.BLOB); debugCodeAssign("Blob", TraceObject.BLOB, id, "createClob()"); checkClosedForWrite(); try { Value v = ValueLobDb.createTempBlob( new ByteArrayInputStream(Utils.EMPTY_BYTES), 0); session.addTemporaryLob(v); return new JdbcBlob(this, v, id); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * Create a new empty NClob object. * * @return the object */ @Override public NClob createNClob() throws SQLException { try { int id = getNextId(TraceObject.CLOB); debugCodeAssign("NClob", TraceObject.CLOB, id, "createNClob()"); checkClosedForWrite(); try { Value v = ValueLobDb.createTempClob( new InputStreamReader( new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0); session.addTemporaryLob(v); return new JdbcClob(this, v, id); } finally { afterWriting(); } } catch (Exception e) { throw logAndConvert(e); } } /** * [Not supported] Create a new empty SQLXML object. */ @Override public SQLXML createSQLXML() throws SQLException { throw unsupported("SQLXML"); } /** * [Not supported] Create a new empty Array object. */ @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { throw unsupported("createArray"); } /** * [Not supported] Create a new empty Struct object. */ @Override 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. */ @Override 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; } } /** * Set a client property. This method always throws a * SQLClientInfoException. * * @param name the name of the property (ignored) * @param value the value (ignored) */ @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { try { if (isDebugEnabled()) { debugCode("setClientInfo(" + quote(name) + ", " + quote(value) + ");"); } checkClosed(); // we don't have any client properties, so just throw throw new SQLClientInfoException(); } catch (Exception e) { throw convertToClientInfoException(logAndConvert(e)); } } /** * Get the client properties. * * @return the property list */ @Override public Properties getClientInfo() throws SQLException { try { if (isDebugEnabled()) { debugCode("getClientInfo();"); } checkClosed(); Properties p = new Properties(); return p; } catch (Exception e) { throw logAndConvert(e); } } /** * Set the client properties. This method always throws a * SQLClientInfoException. * * @param properties the properties (ignored) */ @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { try { if (isDebugEnabled()) { debugCode("setClientInfo(properties);"); } checkClosed(); // we don't have any client properties, so just throw throw new SQLClientInfoException(); } catch (Exception e) { throw convertToClientInfoException(logAndConvert(e)); } } /** * Get a client property. * * @param name the client info name (ignored) * @return the property value */ @Override public String getClientInfo(String name) throws SQLException { try { if (isDebugEnabled()) { debugCodeCall("getClientInfo", name); } checkClosed(); Properties p = getClientInfo(); String s = p.getProperty(name); if (s == null) { throw new SQLClientInfoException(); } return s; } catch (Exception e) { throw logAndConvert(e); } } /** * Return an object of this class if possible. * * @param iface the class * @return this */ @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { if (isWrapperFor(iface)) { return (T) this; } throw DbException.getInvalidValueException("iface", iface); } /** * Checks if unwrap can return an object of this class. * * @param iface the class * @return whether or not the interface is assignable from this class */ @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface != null && iface.isAssignableFrom(getClass()); } /** * 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 = ValueLobDb.createTempClob(x, length); session.addTemporaryLob(v); 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 = ValueLobDb.createTempBlob(x, length); session.addTemporaryLob(v); return v; } /** * [Not supported] Java 1.7 */ public String getSchema() { checkClosed(); return null; } /** * [Not supported] Java 1.7 * * @param schema the schema */ public void setSchema(String schema) { checkClosed(); // not supported } /** * [Not supported] Java 1.7 * * @param executor the executor used by this method */ public void abort(Executor executor) { // not supported checkClosed(); } /** * [Not supported] Java 1.7 * * @param executor the executor used by this method * @param milliseconds the TCP connection timeout */ public void setNetworkTimeout(Executor executor, int milliseconds) { // not supported checkClosed(); } /** * [Not supported] Java 1.7 */ public int getNetworkTimeout() { checkClosed(); return 0; } /** * INTERNAL */ @Override 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 = JdbcUtils.deserialize(v.getBytesNoCopy()); break; } default: o = v.getObject(); } return o; } CompareMode getCompareMode() { return compareMode; } /** * INTERNAL */ public void setTraceLevel(int level) { trace.setLevel(level); } }