package liquibase.executor.jvm; import liquibase.change.Change; import liquibase.database.Database; import liquibase.database.DatabaseConnection; import liquibase.database.OfflineConnection; import liquibase.database.PreparedStatementFactory; import liquibase.database.core.OracleDatabase; import liquibase.database.jvm.JdbcConnection; import liquibase.exception.DatabaseException; import liquibase.executor.AbstractExecutor; import liquibase.executor.Executor; import liquibase.logging.LogFactory; import liquibase.logging.Logger; import liquibase.sql.UnparsedSql; import liquibase.sql.visitor.SqlVisitor; import liquibase.statement.*; import liquibase.statement.core.RawSqlStatement; import liquibase.util.JdbcUtils; import liquibase.util.StringUtils; import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Class to simplify execution of SqlStatements. Based heavily on <a href="http://static.springframework.org/spring/docs/2.0.x/reference/jdbc.html">Spring's JdbcTemplate</a>. * <br><br> * <b>Note: This class is currently intended for Liquibase-internal use only and may change without notice in the future</b> */ @SuppressWarnings({"unchecked"}) public class JdbcExecutor extends AbstractExecutor { private Logger log = LogFactory.getLogger(); @Override public boolean updatesDatabase() { return true; } public Object execute(StatementCallback action, List<SqlVisitor> sqlVisitors) throws DatabaseException { DatabaseConnection con = database.getConnection(); Statement stmt = null; try { if (con instanceof OfflineConnection) { throw new DatabaseException("Cannot execute commands against an offline database"); } stmt = ((JdbcConnection) con).getUnderlyingConnection().createStatement(); Statement stmtToUse = stmt; return action.doInStatement(stmtToUse); } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; String url; if (con.isClosed()) { url = "CLOSED CONNECTION"; } else { url = con.getURL(); } throw new DatabaseException("Error executing SQL " + StringUtils.join(applyVisitors(action.getStatement(), sqlVisitors), "; on "+ url)+": "+ex.getMessage(), ex); } finally { JdbcUtils.closeStatement(stmt); } } public Object execute(CallableStatementCallback action, List<SqlVisitor> sqlVisitors) throws DatabaseException { DatabaseConnection con = database.getConnection(); if (con instanceof OfflineConnection) { throw new DatabaseException("Cannot execute commands against an offline database"); } CallableStatement stmt = null; try { String sql = applyVisitors(action.getStatement(), sqlVisitors)[0]; stmt = ((JdbcConnection) con).getUnderlyingConnection().prepareCall(sql); return action.doInCallableStatement(stmt); } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; throw new DatabaseException("Error executing SQL " + StringUtils.join(applyVisitors(action.getStatement(), sqlVisitors), "; on "+ con.getURL())+": "+ex.getMessage(), ex); } finally { JdbcUtils.closeStatement(stmt); } } @Override public void execute(final SqlStatement sql) throws DatabaseException { execute(sql, new ArrayList<SqlVisitor>()); } @Override public void execute(final SqlStatement sql, final List<SqlVisitor> sqlVisitors) throws DatabaseException { if(sql instanceof ExecutablePreparedStatement) { ((ExecutablePreparedStatement) sql).execute(new PreparedStatementFactory((JdbcConnection)database.getConnection())); return; } execute(new ExecuteStatementCallback(sql, sqlVisitors), sqlVisitors); } public Object query(final SqlStatement sql, final ResultSetExtractor rse) throws DatabaseException { return query(sql, rse, new ArrayList<SqlVisitor>()); } public Object query(final SqlStatement sql, final ResultSetExtractor rse, final List<SqlVisitor> sqlVisitors) throws DatabaseException { if (sql instanceof CallableSqlStatement) { return execute(new QueryCallableStatementCallback(sql, rse), sqlVisitors); } return execute(new QueryStatementCallback(sql, rse, sqlVisitors), sqlVisitors); } public List query(SqlStatement sql, RowMapper rowMapper) throws DatabaseException { return query(sql, rowMapper, new ArrayList()); } public List query(SqlStatement sql, RowMapper rowMapper, List<SqlVisitor> sqlVisitors) throws DatabaseException { return (List) query(sql, new RowMapperResultSetExtractor(rowMapper), sqlVisitors); } public Object queryForObject(SqlStatement sql, RowMapper rowMapper) throws DatabaseException { return queryForObject(sql, rowMapper, new ArrayList()); } public Object queryForObject(SqlStatement sql, RowMapper rowMapper, List<SqlVisitor> sqlVisitors) throws DatabaseException { List results = query(sql, rowMapper, sqlVisitors); try { return JdbcUtils.requiredSingleResult(results); } catch (DatabaseException e) { throw new DatabaseException("Expected single row from " + sql + " but got "+results.size(), e); } } @Override public <T> T queryForObject(SqlStatement sql, Class<T> requiredType) throws DatabaseException { return (T) queryForObject(sql, requiredType, new ArrayList()); } @Override public <T> T queryForObject(SqlStatement sql, Class<T> requiredType, List<SqlVisitor> sqlVisitors) throws DatabaseException { return (T) queryForObject(sql, getSingleColumnRowMapper(requiredType), sqlVisitors); } @Override public long queryForLong(SqlStatement sql) throws DatabaseException { return queryForLong(sql, new ArrayList()); } @Override public long queryForLong(SqlStatement sql, List<SqlVisitor> sqlVisitors) throws DatabaseException { Number number = (Number) queryForObject(sql, Long.class, sqlVisitors); return (number != null ? number.longValue() : 0); } @Override public int queryForInt(SqlStatement sql) throws DatabaseException { return queryForInt(sql, new ArrayList()); } @Override public int queryForInt(SqlStatement sql, List<SqlVisitor> sqlVisitors) throws DatabaseException { Number number = (Number) queryForObject(sql, Integer.class, sqlVisitors); return (number != null ? number.intValue() : 0); } @Override public List queryForList(SqlStatement sql, Class elementType) throws DatabaseException { return queryForList(sql, elementType, new ArrayList()); } @Override public List queryForList(SqlStatement sql, Class elementType, List<SqlVisitor> sqlVisitors) throws DatabaseException { return query(sql, getSingleColumnRowMapper(elementType), sqlVisitors); } @Override public List<Map<String, ?>> queryForList(SqlStatement sql) throws DatabaseException { return queryForList(sql, new ArrayList()); } @Override public List<Map<String, ?>> queryForList(SqlStatement sql, List<SqlVisitor> sqlVisitors) throws DatabaseException { //noinspection unchecked return (List<Map<String, ?>>) query(sql, getColumnMapRowMapper(), sqlVisitors); } @Override public int update(final SqlStatement sql) throws DatabaseException { return update(sql, new ArrayList()); } @Override public int update(final SqlStatement sql, final List<SqlVisitor> sqlVisitors) throws DatabaseException { if (sql instanceof CallableSqlStatement) { throw new DatabaseException("Direct update using CallableSqlStatement not currently implemented"); } class UpdateStatementCallback implements StatementCallback { @Override public Object doInStatement(Statement stmt) throws SQLException, DatabaseException { String[] sqlToExecute = applyVisitors(sql, sqlVisitors); if (sqlToExecute.length != 1) { throw new DatabaseException("Cannot call update on Statement that returns back multiple Sql objects"); } log.debug("Executing UPDATE database command: "+sqlToExecute[0]); return stmt.executeUpdate(sqlToExecute[0]); } @Override public SqlStatement getStatement() { return sql; } } return (Integer) execute(new UpdateStatementCallback(), sqlVisitors); } /** * Create a new RowMapper for reading columns as key-value pairs. * * @return the RowMapper to use * @see ColumnMapRowMapper */ protected RowMapper getColumnMapRowMapper() { return new ColumnMapRowMapper(); } /** * Create a new RowMapper for reading result objects from a single column. * * @param requiredType the type that each result object is expected to match * @return the RowMapper to use * @see SingleColumnRowMapper */ protected RowMapper getSingleColumnRowMapper(Class requiredType) { return new SingleColumnRowMapper(requiredType); } @Override public void comment(String message) throws DatabaseException { LogFactory.getLogger().debug(message); } /** * Adapter to enable use of a RowCallbackHandler inside a ResultSetExtractor. * <p>Uses a regular ResultSet, so we have to be careful when using it: * We don't use it for navigating since this could lead to unpredictable consequences. */ private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor { private final RowCallbackHandler rch; public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) { this.rch = rch; } @Override public Object extractData(ResultSet rs) throws SQLException { while (rs.next()) { this.rch.processRow(rs); } return null; } } private class ExecuteStatementCallback implements StatementCallback { private final SqlStatement sql; private final List<SqlVisitor> sqlVisitors; private ExecuteStatementCallback(SqlStatement sql, List<SqlVisitor> sqlVisitors) { this.sql = sql; this.sqlVisitors = sqlVisitors; } @Override public Object doInStatement(Statement stmt) throws SQLException, DatabaseException { for (String statement : applyVisitors(sql, sqlVisitors)) { if (database instanceof OracleDatabase) { while (statement.matches("(?s).*[\\s\\r\\n]*/[\\s\\r\\n]*$")) { //all trailing /'s statement = statement.replaceFirst("[\\s\\r\\n]*/[\\s\\r\\n]*$", ""); } } log.debug("Executing EXECUTE database command: "+statement); if (statement.contains("?")) { stmt.setEscapeProcessing(false); } try { stmt.execute(statement); } catch (Throwable e) { throw new DatabaseException(e.getMessage()+ " [Failed SQL: "+statement+"]", e); } } return null; } @Override public SqlStatement getStatement() { return sql; } } private class QueryStatementCallback implements StatementCallback { private final SqlStatement sql; private final List<SqlVisitor> sqlVisitors; private final ResultSetExtractor rse; private QueryStatementCallback(SqlStatement sql, ResultSetExtractor rse, List<SqlVisitor> sqlVisitors) { this.sql = sql; this.rse = rse; this.sqlVisitors = sqlVisitors; } @Override public Object doInStatement(Statement stmt) throws SQLException, DatabaseException { ResultSet rs = null; try { String[] sqlToExecute = applyVisitors(sql, sqlVisitors); if (sqlToExecute.length != 1) { throw new DatabaseException("Can only query with statements that return one sql statement"); } log.debug("Executing QUERY database command: "+sqlToExecute[0]); rs = stmt.executeQuery(sqlToExecute[0]); ResultSet rsToUse = rs; return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); } } @Override public SqlStatement getStatement() { return sql; } } private class QueryCallableStatementCallback implements CallableStatementCallback { private final SqlStatement sql; private final ResultSetExtractor rse; private QueryCallableStatementCallback(SqlStatement sql, ResultSetExtractor rse) { this.sql = sql; this.rse = rse; } @Override public Object doInCallableStatement(CallableStatement cs) throws SQLException, DatabaseException { ResultSet rs = null; try { rs = cs.executeQuery(); return rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); } } @Override public SqlStatement getStatement() { return sql; } } }