All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scriptella.jdbc.SqlExecutor Maven / Gradle / Ivy

The newest version!
/*
 * 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 params = new ArrayList();
    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();
    }
}