// Copyright (c) 2003 Dustin Sallings <dustin@spy.net> package net.spy.db; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import net.spy.SpyObject; /** * Run a SQL script from an InputStream. * * <p> * SQL scripts run by this class are slightly special over normal SQL * scripts in that they require a particular structure. * </p> * <p> * Lines beginning with a SQL comment (--) are logged at info level. * Queries are executed when a semicolon is encountered on a line all by * itself. Empty lines are ignored. Everything else is concatenated to * form the next query to execute. Queries that return a result set will * throw a SQLException. * </p> */ public class SQLRunner extends SpyObject { private final Connection connection; private int timeout=0; /** * Get an instance of SQLRunner. */ public SQLRunner(Connection conn) { super(); this.connection=conn; } /** * Set the query timeout (in seconds). */ public void setTimeout(int to) { this.timeout=to; } /** * Run the given script as a transaction. * * @param is the stream containing the script * @throws SQLException if there's a SQL problem with this script * @throws IOException if there's a problem reading the script */ public void runScript(InputStream is) throws SQLException, IOException { runScript(is, false, false); } /** * Run the given script. * * @param is the stream containing the script * @param autocommit if true, commit after each statement * @param errok if true, ignore SQL exceptions on each statement * @throws SQLException if there's a SQL problem executing the script * @throws IOException if there's a problem reading the script */ public void runScript(InputStream is, boolean autocommit, boolean errok) throws SQLException, IOException { // Get a LineNumberReader from this stream so we can process it one // line at a time LineNumberReader lr=new LineNumberReader(new InputStreamReader(is)); boolean successful=false; boolean origAutoCommit=true; try { // Set the autocommit setting origAutoCommit=connection.getAutoCommit(); if(origAutoCommit != autocommit) { connection.setAutoCommit(autocommit); } // Execute the script executeScript(lr, errok); // We're finished, commit if(!autocommit) { connection.commit(); } successful=true; } finally { // Close the reader lr.close(); // If we weren't successful, but we did at least get a // connection, clean it up. if(connection!=null) { // Stuff to do when we weren't successful if(!successful) { try { connection.rollback(); } catch(SQLException e) { getLogger().warn("Error rolling back", e); } } // Reset the autocommit if it was set to false if(origAutoCommit != autocommit) { try { connection.setAutoCommit(origAutoCommit); } catch(SQLException e) { getLogger().warn("Error resetting autocommit"); } } } // successful check } // finally block } private void executeScript(LineNumberReader lr, boolean errok) throws SQLException, IOException { String curline=null; StringBuilder query=new StringBuilder(1024); while( (curline=lr.readLine()) != null) { curline=curline.trim(); if(curline.equals(";")) { // Execute the current query Statement st=connection.createStatement(); st.setQueryTimeout(timeout); int affected=0; long starttime=System.currentTimeMillis(); try { affected=st.executeUpdate(query.toString()); } catch(SQLException e) { if(errok) { // log the exception getLogger().warn("Ignoring problem executing %s", query, e); } else { throw e; } } finally { st.close(); } long stoptime=System.currentTimeMillis(); st=null; getLogger().info("Affected %d %s in %dms", affected, affected==1?"row":"row", (stoptime-starttime)); // Clear out the string buffer query.delete(0, query.length() + 1); } else if(curline.startsWith("--")) { // Comment to be logged getLogger().info(lr.getLineNumber() + ": " + curline); } else { // Get more query if(curline.length() > 0) { query.append(curline); query.append("\n"); } } } // All lines } // executeScript() }