/* * The Apache Software License, Version 1.1 * * Copyright (c) 2000-2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Ant", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */
package org.apache.tools.ant.taskdefs;
import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.types.FileSet;
import java.io.File; import java.io.PrintStream; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.Reader; import java.io.BufferedReader; import java.io.StringReader; import java.io.FileReader; import java.io.InputStreamReader; import java.io.FileInputStream; import java.util.Enumeration; import java.util.StringTokenizer; import java.util.Vector;
import java.sql.Connection; import java.sql.Statement; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.ResultSet; import java.sql.ResultSetMetaData;
/**
* Executes a series of SQL statements on a database using JDBC.
*
* <p>Statements can
* either be read in from a text file using the <i>src</i> attribute or from
* between the enclosing SQL tags.</p>
*
* <p>Multiple statements can be provided, separated by semicolons (or the
* defined <i>delimiter</i>). Individual lines within the statements can be
* commented using either --, // or REM at the start of the line.</p>
*
* <p>The <i>autocommit</i> attribute specifies whether auto-commit should be
* turned on or off whilst executing the statements. If auto-commit is turned
* on each statement will be executed and committed. If it is turned off the
* statements will all be executed as one transaction.</p>
*
* <p>The <i>onerror</i> attribute specifies how to proceed when an error occurs
* during the execution of one of the statements.
* The possible values are: <b>continue</b> execution, only show the error;
* <b>stop</b> execution and commit transaction;
* and <b>abort</b> execution and transaction and fail task.</p>
* * @author <a href="mailto:[EMAIL PROTECTED]">Jeff Martin</a> * @author <A href="mailto:[EMAIL PROTECTED]">Michael McCallum</A> * @author <A href="mailto:[EMAIL PROTECTED]">Tim Stephenson</A> * * @since Ant 1.2 * * @ant.task name="sql" category="database" */ public class SQLExec extends JDBCTask {
/** * delimiters we support, "normal" and "row" */ public static class DelimiterType extends EnumeratedAttribute { public static final String NORMAL = "normal"; public static final String ROW = "row"; public String[] getValues() { return new String[] {NORMAL, ROW}; } }
private int goodSql = 0;
private int totalSql = 0;
/** * Database connection */ private Connection conn = null;
/** * files to load */ private Vector filesets = new Vector();
/** * SQL statement */ private Statement statement = null;
/** * SQL input file */ private File srcFile = null;
/** * SQL input command */ private String sqlCommand = "";
/** * SQL transactions to perform */ private Vector transactions = new Vector();
/** * SQL Statement delimiter */ private String delimiter = ";";
/** * The delimiter type indicating whether the delimiter will * only be recognized on a line by itself */ private String delimiterType = DelimiterType.NORMAL;
/** * Print SQL results. */ private boolean print = false;
/** * Print header columns. */ private boolean showheaders = true;
/** * Results Output file. */ private File output = null;
/** * Action to perform if an error is found **/ private String onError = "abort";
/** * Encoding to use when reading SQL statements from a file */ private String encoding = null;
/** * Append to an existing file or overwrite it? */ private boolean append = false;
/** * Keep the format of a sql block? */ private boolean keepformat = false;
/** * Set the name of the SQL file to be run. * Required unless statements are enclosed in the build file */ public void setSrc(File srcFile) { this.srcFile = srcFile; }
/** * Set an inline SQL command to execute. * NB: Properties are not expanded in this text. */ public void addText(String sql) { this.sqlCommand += sql; }
/** * Adds a set of files (nested fileset attribute). */ public void addFileset(FileSet set) { filesets.addElement(set); }
/** * Add a SQL transaction to execute */ public Transaction createTransaction() { Transaction t = new Transaction(); transactions.addElement(t); return t; }
/** * Set the file encoding to use on the SQL files read in * * @param encoding the encoding to use on the files */ public void setEncoding(String encoding) { this.encoding = encoding; }
/** * Set the delimiter that separates SQL statements; * optional, default ";" * * <p>For example, set this to "go" and delimitertype to "ROW" for * Sybase ASE or MS SQL Server.</p> */ public void setDelimiter(String delimiter) { this.delimiter = delimiter; }
/** * Set the delimiter type: "normal" or "row" (default "normal"). * * <p>The delimiter type takes two values - normal and row. Normal * means that any occurence of the delimiter terminate the SQL * command whereas with row, only a line containing just the * delimiter is recognized as the end of the command.</p> */ public void setDelimiterType(DelimiterType delimiterType) { this.delimiterType = delimiterType.getValue(); }
/** * Print result sets from the statements; * optional, default false */ public void setPrint(boolean print) { this.print = print; }
/** * Print headers for result sets from the * statements; optional, default true. */ public void setShowheaders(boolean showheaders) { this.showheaders = showheaders; }
/** * Set the output file; * optional, defaults to the Ant log. */ public void setOutput(File output) { this.output = output; }
/** * whether output should be appended to or overwrite * an existing file. Defaults to false. * * @since Ant 1.5 */ public void setAppend(boolean append) { this.append = append; }
/** * Action to perform when statement fails: continue, stop, or abort * optional; default "abort" */ public void setOnerror(OnError action) { this.onError = action.getValue(); }
/** * whether or not format should be preserved. * Defaults to false. * * @param keepformat The keepformat to set */ public void setKeepformat(boolean keepformat) { this.keepformat = keepformat; }
/** * Load the sql file and then execute it */ public void execute() throws BuildException { Vector savedTransaction = (Vector) transactions.clone(); String savedSqlCommand = sqlCommand;
sqlCommand = sqlCommand.trim();
try {
if (srcFile == null && sqlCommand.length() == 0
&& filesets.isEmpty()) {
if (transactions.size() == 0) {
throw new BuildException("Source file or fileset, "
+ "transactions or sql statement "
+ "must be set!", location);
}
}
if (srcFile != null && !srcFile.exists()) {
throw new BuildException("Source file does not exist!", location);
}
// deal with the filesets for (int i = 0; i < filesets.size(); i++) { FileSet fs = (FileSet) filesets.elementAt(i); DirectoryScanner ds = fs.getDirectoryScanner(project); File srcDir = fs.getDir(project);
String[] srcFiles = ds.getIncludedFiles();
// Make a transaction for each file for (int j = 0 ; j < srcFiles.length ; j++) { Transaction t = createTransaction(); t.setSrc(new File(srcDir, srcFiles[j])); } }
// Make a transaction group for the outer command Transaction t = createTransaction(); t.setSrc(srcFile); t.addText(sqlCommand); conn = getConnection(); if (!isValidRdbms(conn)) { return; } try { statement = conn.createStatement();
PrintStream out = System.out;
try {
if (output != null) {
log("Opening PrintStream to output file " + output,
Project.MSG_VERBOSE);
out = new PrintStream(
new BufferedOutputStream(
new FileOutputStream(output
.getAbsolutePath(),
append)));
}
// Process all transactions for (Enumeration e = transactions.elements(); e.hasMoreElements();) {
((Transaction) e.nextElement()).runTransaction(out);
if (!isAutocommit()) {
log("Commiting transaction", Project.MSG_VERBOSE);
conn.commit();
}
}
} finally {
if (out != null && out != System.out) {
out.close();
}
}
} catch (IOException e){
if (!isAutocommit() && conn != null && onError.equals("abort")) {
try {
conn.rollback();
} catch (SQLException ex) {}
}
throw new BuildException(e, location);
} catch (SQLException e){
if (!isAutocommit() && conn != null && onError.equals("abort")) {
try {
conn.rollback();
} catch (SQLException ex) {}
}
throw new BuildException(e, location);
} finally {
try {
if (statement != null) {
statement.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {}
}
log(goodSql + " of " + totalSql + " SQL statements executed successfully"); } finally { transactions = savedTransaction; sqlCommand = savedSqlCommand; } }
/** * read in lines and execute them */ protected void runStatements(Reader reader, PrintStream out) throws SQLException, IOException { StringBuffer sql = new StringBuffer(); String line = "";
BufferedReader in = new BufferedReader(reader);
while ((line = in.readLine()) != null){ if (!keepformat) { line = line.trim(); } line = project.replaceProperties(line); if (!keepformat) { if (line.startsWith("//")) { continue; } if (line.startsWith("--")) { continue; } StringTokenizer st = new StringTokenizer(line); if (st.hasMoreTokens()) { String token = st.nextToken(); if ("REM".equalsIgnoreCase(token)) { continue; } } }
if (!keepformat) { sql.append(" " + line); } else { sql.append("\n" + line); }
// SQL defines "--" as a comment to EOL // and in Oracle it may contain a hint // so we cannot just remove it, instead we must end it if (!keepformat) { if (line.indexOf("--") >= 0) { sql.append("\n"); } } if ((delimiterType.equals(DelimiterType.NORMAL) && sql.toString().endsWith(delimiter)) || (delimiterType.equals(DelimiterType.ROW) && line.equals(delimiter))) { log("SQL: " + sql, Project.MSG_VERBOSE); execSQL(sql.substring(0, sql.length() - delimiter.length()), out); sql.replace(0, sql.length(), ""); } } // Catch any statements not followed by ; if (!sql.equals("")){ execSQL(sql.toString(), out); } }
/**
* Exec the sql statement.
*/
protected void execSQL(String sql, PrintStream out) throws SQLException {
// Check and ignore empty statements
if ("".equals(sql.trim())) {
return;
}
try { totalSql++; if (!statement.execute(sql)) { log(statement.getUpdateCount() + " rows affected", Project.MSG_VERBOSE); } else { if (print) { printResults(out); } }
SQLWarning warning = conn.getWarnings(); while (warning != null){ log(warning + " sql warning", Project.MSG_VERBOSE); warning = warning.getNextWarning(); } conn.clearWarnings(); goodSql++; } catch (SQLException e) { log("Failed to execute: " + sql, Project.MSG_ERR); if (!onError.equals("continue")) { throw e; } log(e.toString(), Project.MSG_ERR); } }
/**
* print any results in the statement.
*/
protected void printResults(PrintStream out) throws java.sql.SQLException {
ResultSet rs = null;
do {
rs = statement.getResultSet();
if (rs != null) {
log("Processing new result set.", Project.MSG_VERBOSE);
ResultSetMetaData md = rs.getMetaData();
int columnCount = md.getColumnCount();
StringBuffer line = new StringBuffer();
if (showheaders) {
for (int col = 1; col < columnCount; col++) {
line.append(md.getColumnName(col));
line.append(",");
}
line.append(md.getColumnName(columnCount));
out.println(line);
line.setLength(0);
}
while (rs.next()) {
boolean first = true;
for (int col = 1; col <= columnCount; col++) {
String columnValue = rs.getString(col);
if (columnValue != null) {
columnValue = columnValue.trim();
}
if (first) { first = false; } else { line.append(","); } line.append(columnValue); } out.println(line); line.setLength(0); } } } while (statement.getMoreResults()); out.println(); }
/** * The action a task should perform on an error, * one of "continue", "stop" and "abort" */ public static class OnError extends EnumeratedAttribute { public String[] getValues() { return new String[] {"continue", "stop", "abort"}; } }
/** * Contains the definition of a new transaction element. * Transactions allow several files or blocks of statements * to be executed using the same JDBC connection and commit * operation in between. */ public class Transaction { private File tSrcFile = null; private String tSqlCommand = "";
/** * */ public void setSrc(File src) { this.tSrcFile = src; }
/** * */ public void addText(String sql) { this.tSqlCommand += sql; }
/** * */ private void runTransaction(PrintStream out) throws IOException, SQLException { if (tSqlCommand.length() != 0) { log("Executing commands", Project.MSG_INFO); runStatements(new StringReader(tSqlCommand), out); }
if (tSrcFile != null) { log("Executing file: " + tSrcFile.getAbsolutePath(), Project.MSG_INFO); Reader reader = (encoding == null) ? new FileReader(tSrcFile) : new InputStreamReader( new FileInputStream(tSrcFile), encoding); try { runStatements(reader, out); } finally { reader.close(); } } } }
}