/* * Copyright 2006-2012 The Scriptella Project Team. * * 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 scriptella.jdbc; import scriptella.core.EtlCancelledException; import scriptella.spi.AbstractConnection; import scriptella.spi.ParametersCallback; import scriptella.spi.QueryCallback; import scriptella.spi.Resource; import scriptella.util.StringUtils; import java.io.Closeable; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.sql.SQLException; import java.sql.SQLWarning; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * SQL statements executor. * * @author Fyodor Kupolov * @version 1.0 */ class SqlExecutor extends SqlParserBase implements Closeable { private static final Logger LOG = Logger.getLogger(SqlExecutor.class.getName()); protected final Resource resource; protected final JdbcConnection connection; protected final StatementCache cache; private QueryCallback callback; private ParametersCallback paramsCallback; private List<Object> params = new ArrayList<Object>(); private int updateCount;//number of updated rows private final AbstractConnection.StatementCounter counter; private SqlTokenizer cachedTokenizer; public SqlExecutor(final Resource resource, final JdbcConnection connection) { this.resource = resource; this.connection = connection; cache = connection.newStatementCache(); counter = connection.getStatementCounter(); } final void execute(final ParametersCallback parametersCallback) { execute(parametersCallback, null); } final void execute(final ParametersCallback parametersCallback, final QueryCallback queryCallback) { paramsCallback = parametersCallback; callback = queryCallback; updateCount = 0; SqlTokenizer tok = cachedTokenizer; boolean cache = false; if (tok==null) { //If not cached try { final Reader reader = resource.open(); tok = new SqlReaderTokenizer(reader, connection.separator, connection.separatorSingleLine, connection.keepformat); cache = reader instanceof StringReader; if (cache) { //If resource is a String - allow caching tok = new CachedSqlTokenizer(tok); } } catch (IOException e) { throw new JdbcException("Failed to open resource", e); } } parse(tok); //We should remember cached tokenizer only if all statements were parsed //i.e. no errors occured if (cache) { cachedTokenizer=tok; } } int getUpdateCount() { return updateCount; } @Override protected String handleParameter(final String name, final boolean expression, boolean jdbcParam) { Object p; if (expression) { p = connection.getParametersParser().evaluate(name, paramsCallback); } else { p = paramsCallback.getParameter(name); } if (jdbcParam) { //if insert as prepared stmt parameter params.add(p); return "?"; } else { //otherwise return string representation. //todo we need to defines rules for toString transformations return p == null ? super.handleParameter(name, expression, jdbcParam) : p.toString(); } } @Override public void statementParsed(final String sql) { EtlCancelledException.checkEtlCancelled(); StatementWrapper sw = null; try { sw = cache.prepare(sql, params); int updatedRows = -1; if (callback != null) { sw.query(callback, paramsCallback); } else { updatedRows = sw.update(); } logExecutedStatement(sql, params, updatedRows); if (connection.autocommitSize > 0 && (counter.statements % connection.autocommitSize == 0)) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("Committing transaction after " + connection.autocommitSize + " statements"); } connection.commit(); } } catch (SQLException e) { throw new JdbcException("Unable to execute statement", e, sql, params); } catch (JdbcException e) { //if ProviderException has no SQL - attach it if (e.getErrorStatement() == null) { e.setErrorStatement(sql, params); } throw e; //rethrow } finally { params.clear(); if (sw != null) { cache.releaseStatement(sw); } } } private void logExecutedStatement(final String sql, final List<?> parameters, final int updateCount) { counter.statements++; if (updateCount > 0) { this.updateCount += updateCount; } SQLWarning warnings = null; try { warnings = connection.getNativeConnection().getWarnings(); } catch (Exception e) { LOG.log(Level.WARNING, "Failed to obtain SQL warnings", e); } Level level = warnings == null ? Level.FINE : Level.INFO; if (warnings != null) { //If warnings present - use INFO priority level = Level.INFO; } if (LOG.isLoggable(level)) { StringBuilder sb = new StringBuilder(" Executed statement "); sb.append(StringUtils.consoleFormat(sql)); if (!parameters.isEmpty()) { sb.append(", Parameters: ").append(parameters); } if (updateCount >= 0) { sb.append(". Update count: ").append(updateCount); } if (warnings != null) { //Iterate warnings sb.append(". SQL Warnings:"); do { sb.append("\n * ").append(warnings); warnings = warnings.getNextWarning(); } while (warnings != null); try { connection.getNativeConnection().clearWarnings(); } catch (Exception e) { //catch everything because drivers may violate rules and throw any exception LOG.log(Level.WARNING, "Failed to clear SQL warnings", e); } } LOG.log(level, sb.toString()); } } public void close() { cache.close(); } }