/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * 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 org.jkiss.dbeaver.model.impl.jdbc.exec; import org.jkiss.dbeaver.Log; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.model.exec.DBCExecutionSource; import org.jkiss.dbeaver.model.messages.ModelMessages; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet; import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; import org.jkiss.dbeaver.model.exec.jdbc.JDBCStatement; import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils; import org.jkiss.dbeaver.model.qm.QMUtils; import org.jkiss.utils.CommonUtils; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * Managable statement. * Stores information about execution in query manager and operated progress monitor. */ public class JDBCStatementImpl<STATEMENT extends Statement> implements JDBCStatement { private static final Log log = Log.getLog(JDBCStatementImpl.class); protected final JDBCSession connection; protected final STATEMENT original; private String query; private long rsOffset = -1; private long rsMaxRows = -1; private DBCExecutionSource source; private int updateCount; private Throwable executeError; private boolean disableLogging; public JDBCStatementImpl(@NotNull JDBCSession connection, @NotNull STATEMENT original, boolean disableLogging) { this.connection = connection; this.original = original; this.disableLogging = disableLogging; if (isQMLoggingEnabled()) { QMUtils.getDefaultHandler().handleStatementOpen(this); } } protected STATEMENT getOriginal() { return original; } protected boolean isQMLoggingEnabled() { return !disableLogging; } protected void startBlock() { this.connection.getProgressMonitor().startBlock( this, null/*this.query == null ? "?" : JDBCUtils.limitQueryLength(query, 200)*/); } protected void endBlock() { connection.getProgressMonitor().endBlock(); } @Override public void cancelBlock() throws DBException { try { this.cancel(); } catch (SQLException e) { throw new DBException(e, connection.getDataSource()); } } @NotNull @Override public JDBCSession getConnection() { return connection; } //////////////////////////////////////////////////////////////////// // DBC Statement overrides //////////////////////////////////////////////////////////////////// @NotNull @Override public JDBCSession getSession() { return connection; } @Nullable @Override public String getQueryString() { return query; } public void setQueryString(@Nullable String query) { this.query = query; } @Override public boolean executeStatement() throws DBCException { try { return execute(query); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } } @Override public void addToBatch() throws DBCException { try { addBatch(query); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } } @Override public int[] executeStatementBatch() throws DBCException { try { return executeBatch(); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } } @Nullable @Override public JDBCResultSet openResultSet() throws DBCException { // Some driver perform real RS fetch at this moment. // So let's start thge block this.startBlock(); try { return getResultSet(); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } finally { this.endBlock(); } } @Nullable @Override public JDBCResultSet openGeneratedKeysResultSet() throws DBCException { try { return makeResultSet(getOriginal().getGeneratedKeys()); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } } @Override public int getUpdateRowCount() throws DBCException { try { return getUpdateCount(); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } } @Override public boolean nextResults() throws DBCException { try { return getOriginal().getMoreResults(); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } } @Override public void setLimit(long offset, long limit) throws DBCException { int totalRows; if (offset <= 0) { // Just set max row num totalRows = (int)limit; this.rsMaxRows = limit; } else { // Remember limit values - we'll use them in resultset fetch routine this.rsOffset = offset; this.rsMaxRows = limit; totalRows = limit > 0 ? (int)(offset + limit) : -1; } if (totalRows > 0 && connection.getDataSource().getInfo().supportsResultSetLimit()) { try { setMaxRows(totalRows); } catch (SQLException e) { // [JDBC:ODBC] Probably setMaxRows is not supported. Just log this error // We'll use rsOffset and rsMaxRows anyway log.debug(getOriginal().getClass().getName() + ".setMaxRows not supported?", e); } } } @Nullable @Override public DBCExecutionSource getStatementSource() { return this.source; } @Override public void setStatementSource(DBCExecutionSource source) { this.source = source; } @Nullable protected JDBCResultSet makeResultSet(@Nullable ResultSet resultSet) throws SQLException { if (resultSet == null) { return null; } JDBCResultSet dbResult = createResultSetImpl(resultSet); // Scroll original result set if needed if (rsOffset > 0) { JDBCUtils.scrollResultSet(resultSet, rsOffset, !getConnection().getDataSource().getInfo().supportsResultSetScroll()); } if (rsMaxRows > 0 && connection.getDataSource().getInfo().supportsResultSetLimit()) { dbResult.setMaxRows(rsMaxRows); } return dbResult; } protected JDBCResultSet createResultSetImpl(ResultSet resultSet) throws SQLException { return connection.getDataSource().getJdbcFactory().createResultSet(connection, this, resultSet, null, disableLogging); } //////////////////////////////////////////////////////////////////// // Statement overrides //////////////////////////////////////////////////////////////////// protected boolean handleExecuteResult(boolean result) { return result; } protected int handleExecuteResult(int result) { updateCount = result; return result; } protected SQLException handleExecuteError(Throwable ex) { executeError = ex; if (connection.getDataSource().getContainer().getPreferenceStore().getBoolean(ModelPreferences.QUERY_ROLLBACK_ON_ERROR)) { try { if (!connection.isClosed() && !connection.getAutoCommit()) { connection.rollback(); } } catch (SQLException e) { log.error("Can't rollback connection after error (" + ex.getMessage() + ")", e); //$NON-NLS-1$ //$NON-NLS-2$ } } if (ex instanceof SQLException) { return (SQLException) ex; } else { return new SQLException(ModelMessages.model_jdbc_exception_internal_jdbc_driver_error, ex); } } protected void beforeExecute() { this.updateCount = -1; this.executeError = null; if (isQMLoggingEnabled()) { QMUtils.getDefaultHandler().handleStatementExecuteBegin(this); } this.startBlock(); } protected void afterExecute() { this.endBlock(); if (isQMLoggingEnabled()) { QMUtils.getDefaultHandler().handleStatementExecuteEnd(this, this.updateCount, this.executeError); } } //////////////////////////////////// // Executions @Override public boolean execute(String sql) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().execute(sql)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Nullable @Override public JDBCResultSet executeQuery(String sql) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return makeResultSet(getOriginal().executeQuery(sql)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public int executeUpdate(String sql) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().executeUpdate(sql)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public int[] executeBatch() throws SQLException { this.beforeExecute(); try { return getOriginal().executeBatch(); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().executeUpdate(sql, autoGeneratedKeys)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().executeUpdate(sql, columnIndexes)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().executeUpdate(sql, columnNames)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().execute(sql, autoGeneratedKeys)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().execute(sql, columnIndexes)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } @Override public boolean execute(String sql, String[] columnNames) throws SQLException { setQueryString(sql); this.beforeExecute(); try { return handleExecuteResult(getOriginal().execute(sql, columnNames)); } catch (Throwable e) { throw this.handleExecuteError(e); } finally { this.afterExecute(); } } //////////////////////////////////// // Close @Override public void close() { /* // Do not check for warnings here // Sometimes warnings are cached in connection and as a result we got a lot of spam in log // for each closed statement on this connection (MySQL) try { JDBCUtils.reportWarnings(getOriginal().getWarnings()); getOriginal().clearWarnings(); } catch (Throwable e) { log.debug("Can't check for statement warnings", e); } */ if (isQMLoggingEnabled()) { // Handle close QMUtils.getDefaultHandler().handleStatementClose(this, updateCount); } // Close statement try { getOriginal().close(); } catch (Throwable e) { log.error("Can't close statement", e); //$NON-NLS-1$ } } //////////////////////////////////// // Other @Override public int getMaxFieldSize() throws SQLException { return getOriginal().getMaxFieldSize(); } @Override public void setMaxFieldSize(int max) throws SQLException { getOriginal().setMaxFieldSize(max); } @Override public int getMaxRows() throws SQLException { return getOriginal().getMaxRows(); } @Override public void setMaxRows(int max) throws SQLException { getOriginal().setMaxRows(max); } @Override public void setEscapeProcessing(boolean enable) throws SQLException { getOriginal().setEscapeProcessing(enable); } @Override public int getQueryTimeout() throws SQLException { return getOriginal().getQueryTimeout(); } @Override public void setQueryTimeout(int seconds) throws SQLException { getOriginal().setQueryTimeout(seconds); } @Override public void cancel() throws SQLException { getOriginal().cancel(); } @Override public SQLWarning getWarnings() throws SQLException { return getOriginal().getWarnings(); } @Override public void clearWarnings() throws SQLException { getOriginal().clearWarnings(); } @Override public void setCursorName(String name) throws SQLException { getOriginal().setCursorName(name); } @Nullable @Override public JDBCResultSet getResultSet() throws SQLException { return makeResultSet(getOriginal().getResultSet()); } @Nullable @Override public Throwable[] getStatementWarnings() throws DBCException { try { List<Throwable> warnings = null; for (SQLWarning warning = getWarnings(); warning != null; warning = warning.getNextWarning()) { if (warning.getMessage() == null && warning.getErrorCode() == 0) { // Skip trash [Excel driver] continue; } if (warnings == null) { warnings = new ArrayList<>(); } if (warnings.contains(warning)) { // Cycle break; } warnings.add(warning); } if (!CommonUtils.isEmpty(warnings)) { try { clearWarnings(); } catch (Throwable e) { log.debug("Internal error during clearWarnings", e); } } return warnings == null ? null : warnings.toArray(new Throwable[warnings.size()]); } catch (SQLException e) { throw new DBCException(e, getSession().getDataSource()); } } @Override public void setStatementTimeout(int timeout) throws DBCException { try { getOriginal().setQueryTimeout(timeout); } catch (SQLException e) { throw new DBCException(e, connection.getDataSource()); } } @Override public int getUpdateCount() throws SQLException { return (updateCount = getOriginal().getUpdateCount()); } @Override public boolean getMoreResults() throws SQLException { return getOriginal().getMoreResults(); } @Override public void setFetchDirection(int direction) throws SQLException { getOriginal().setFetchDirection(direction); } @Override public int getFetchDirection() throws SQLException { return getOriginal().getFetchDirection(); } @Override public void setFetchSize(int rows) throws SQLException { getOriginal().setFetchSize(rows); } @Override public int getFetchSize() throws SQLException { return getOriginal().getFetchSize(); } @Override public int getResultSetConcurrency() throws SQLException { return getOriginal().getResultSetConcurrency(); } @Override public int getResultSetType() throws SQLException { return getOriginal().getResultSetType(); } @Override public void addBatch(String sql) throws SQLException { getOriginal().addBatch(sql); } @Override public void clearBatch() throws SQLException { getOriginal().clearBatch(); } @Override public boolean getMoreResults(int current) throws SQLException { return getOriginal().getMoreResults(current); } @Nullable @Override public ResultSet getGeneratedKeys() throws SQLException { return makeResultSet(getOriginal().getGeneratedKeys()); } @Override public int getResultSetHoldability() throws SQLException { return getOriginal().getResultSetHoldability(); } @Override public boolean isClosed() throws SQLException { return getOriginal().isClosed(); } @Override public void setPoolable(boolean poolable) throws SQLException { getOriginal().setPoolable(poolable); } @Override public boolean isPoolable() throws SQLException { return getOriginal().isPoolable(); } @Override public void closeOnCompletion() throws SQLException { JDBCUtils.callMethod17(getOriginal(), "closeOnCompletion", null, null); } @Override public boolean isCloseOnCompletion() throws SQLException { Boolean closeOnCompletion = JDBCUtils.callMethod17(getOriginal(), "isCloseOnCompletion", Boolean.TYPE, null); return closeOnCompletion == null ? false : closeOnCompletion; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return getOriginal().unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return getOriginal().isWrapperFor(iface); } @Override public String toString() { return "JDBC Statement [" + query + "]"; } }