diff --git a/docs/sql.html b/docs/sql.html index 9f150c0ee..ec12391c8 100644 --- a/docs/sql.html +++ b/docs/sql.html @@ -10,6 +10,12 @@

Multiple statements can be set and each statement is delimited from the next use a semi-colon. Individual lines within the statements can be commented using either -- or // at the start of the line.

The auto-commit 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 commited. If it is turned off the statements will all be executed as one transaction.

+ +

The onerror attribute specifies how to preceed when an error occurs during the execution of one of the statements. +The possible values are: continue execution, only show the error; +stop execution and commit transaction; +and abort execution and transaction and fail task. +

Parameters

@@ -67,7 +73,49 @@ + + + + + + + + + + + + + + + +
Classpath used to load driver No (use system classpath)
onerrorAction to perform when statement fails: continue, stop, abortNo, default "abort"
rdbmsExecute task only if this rdbmsNo (no restriction)
versionExecute task only if rdbms version matchNo (no restriction)
+ +

Parameters specified as nested elements

+

transaction

+

Use nested <transaction> +elements to specify multiple blocks of commands to the executed +executed in the same connection but different transactions. This +is particularly usefull when there are multiple files to execute +on the same schema.

+ + + + + + + + + + +
AttributeDescriptionRequired
srcFile containing sql statementsYes, unless statements enclosed within tags
+

classpath

+

Sql's classpath attribute is a PATH like structure and can also be set via a nested +classpath element. It is used to load the JDBC classes.

+

+The +

Examples

<sql @@ -114,6 +162,22 @@ update some_table set column1 = column1 + 1 where column2 < 42; ]]></sql>
+

The following connects to the database given in url as the sa user using the org.database.jdbcDriver and executes the sql statements contained within the files data1.sql, data2.sql and data3.sql and then executes the truncate operation on some_other_table.

+ +
<sql + driver="org.database.jdbcDriver" + url="jdbc:database-url" + userid="sa" + password="pass" > + <transaction src="data1.sql" /> + <transaction src="data2.sql" /> + <transaction src="data3.sql" /> + <transaction> + truncate table some_other_table; + </transaction> +</sql> +
+

The following connects to the database given in url as the sa user using the org.database.jdbcDriver and executes the sql statements contained within the file data.sql, with output piped to outputfile.txt, searching /some/jdbc.jar as well as the system classpath for the driver class.

<sql @@ -131,6 +195,25 @@ update some_table set column1 = column1 + 1 where column2 < 42; </sql>
+

The following will only execute if the RDBMS is "oracle" and the version starts with "8.1."

+ +
<sql + driver="org.database.jdbcDriver" + url="jdbc:database-url" + userid="sa" + password="pass" + src="data.sql" + rdbms="oracle" + version="8.1." + > +insert +into table some_table +values(1,2,3,4); + +truncate table some_other_table; +</sql> +
+ diff --git a/src/main/org/apache/tools/ant/AntClassLoader.java b/src/main/org/apache/tools/ant/AntClassLoader.java index 1469ecbf2..a04a1ba34 100644 --- a/src/main/org/apache/tools/ant/AntClassLoader.java +++ b/src/main/org/apache/tools/ant/AntClassLoader.java @@ -168,7 +168,7 @@ public class AntClassLoader extends ClassLoader { * this loader's classpath. */ public Class forceLoadClass(String classname) throws ClassNotFoundException { - project.log("force loading " + classname, Project.MSG_VERBOSE); + project.log("force loading " + classname, Project.MSG_DEBUG); Class theClass = findLoadedClass(classname); if (theClass == null) { @@ -192,7 +192,7 @@ public class AntClassLoader extends ClassLoader { * this loader's classpath. */ public Class forceLoadSystemClass(String classname) throws ClassNotFoundException { - project.log("force system loading " + classname, Project.MSG_VERBOSE); + project.log("force system loading " + classname, Project.MSG_DEBUG); Class theClass = findLoadedClass(classname); if (theClass == null) { @@ -324,21 +324,21 @@ public class AntClassLoader extends ClassLoader { if (useSystemFirst) { try { theClass = findSystemClass(classname); - project.log("Class " + classname + " loaded from system loader", Project.MSG_VERBOSE); + project.log("Class " + classname + " loaded from system loader", Project.MSG_DEBUG); } catch (ClassNotFoundException cnfe) { theClass = findClass(classname); - project.log("Class " + classname + " loaded from ant loader", Project.MSG_VERBOSE); + project.log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG); } } else { try { theClass = findClass(classname); - project.log("Class " + classname + " loaded from ant loader", Project.MSG_VERBOSE); + project.log("Class " + classname + " loaded from ant loader", Project.MSG_DEBUG); } catch (ClassNotFoundException cnfe) { theClass = findSystemClass(classname); - project.log("Class " + classname + " loaded from system loader", Project.MSG_VERBOSE); + project.log("Class " + classname + " loaded from system loader", Project.MSG_DEBUG); } } } @@ -400,7 +400,7 @@ public class AntClassLoader extends ClassLoader { * this loader's classpath. */ public Class findClass(String name) throws ClassNotFoundException { - project.log("Finding class " + name, Project.MSG_VERBOSE); + project.log("Finding class " + name, Project.MSG_DEBUG); try { return findClass(name, classpath); diff --git a/src/main/org/apache/tools/ant/taskdefs/SQLExec.java b/src/main/org/apache/tools/ant/taskdefs/SQLExec.java index d327bb6d7..751c201d2 100644 --- a/src/main/org/apache/tools/ant/taskdefs/SQLExec.java +++ b/src/main/org/apache/tools/ant/taskdefs/SQLExec.java @@ -74,6 +74,8 @@ import java.sql.*; */ public class SQLExec extends Task { + private int goodSql = 0, totalSql = 0; + private Path classpath; private AntClassLoader loader; @@ -123,6 +125,11 @@ public class SQLExec extends Task { */ private String sqlCommand = ""; + /** + * SQL transactions to perform + */ + private Vector transactions = new Vector(); + /** * Print SQL results. */ @@ -138,6 +145,21 @@ public class SQLExec extends Task { */ private File output = null; + /** + * RDBMS Product needed for this SQL. + **/ + private String rdbms = null; + + /** + * RDBMS Version needed for this SQL. + **/ + private String version = null; + + /** + * Action to perform if an error is found + **/ + private String onError = "abort"; + /** * Set the classpath for loading the driver. */ @@ -174,12 +196,21 @@ public class SQLExec extends Task { } /** - * Set the name of the sql file to be run. + * Set the sql command to execute */ public void addText(String sql) { this.sqlCommand += sql; } + /** + * Set the sql command to execute + */ + public Transaction createTransaction() { + Transaction t = new Transaction(); + transactions.addElement(t); + return t; + } + /** * Set the JDBC driver to be used. */ @@ -236,15 +267,44 @@ public class SQLExec extends Task { this.output = output; } + /** + * Set the rdbms required + */ + public void setRdbms(String vendor) { + this.rdbms = vendor.toLowerCase(); + } + + /** + * Set the version required + */ + public void setVersion(String version) { + this.version = version.toLowerCase(); + } + + /** + * Set the action to perform onerror + */ + public void setOnerror(OnError action) { + this.onError = action.getValue(); + } + /** * Load the sql file and then execute it */ public void execute() throws BuildException { sqlCommand = sqlCommand.trim(); - if (srcFile == null && sqlCommand.length() == 0) { - throw new BuildException("Source file or sql statement must be set!", location); + if (srcFile == null && sqlCommand.length() == 0) { + if (transactions.size() == 0) { + throw new BuildException("Source file, transactions or sql statement must be set!", location); + } + } else { + // Make a transaction group for the outer command + Transaction t = createTransaction(); + t.setSrc(srcFile); + t.addText(sqlCommand); } + if (driver == null) { throw new BuildException("Driver attribute must be set!", location); } @@ -294,31 +354,31 @@ public class SQLExec extends Task { throw new SQLException("No suitable Driver for "+url); } + if (!isValidRdbms(conn)) return; + conn.setAutoCommit(autocommit); statement = conn.createStatement(); - if (sqlCommand.length() != 0) { - runStatements(new StringReader(sqlCommand)); - } - - if (srcFile != null) { - runStatements(new FileReader(srcFile)); - } - - if (!autocommit) { - conn.commit(); + // Process all transactions + for (Enumeration e = transactions.elements(); + e.hasMoreElements();) { + ((Transaction) e.nextElement()).runTransaction(); + if (!autocommit) { + log("Commiting transaction", Project.MSG_VERBOSE); + conn.commit(); + } } } catch(IOException e){ - if (!autocommit && conn != null) { + if (!autocommit && conn != null && onError.equals("abort")) { try { conn.rollback(); } catch (SQLException ex) {} } throw new BuildException(e, location); } catch(SQLException e){ - if (!autocommit && conn != null) { + if (!autocommit && conn != null && onError.equals("abort")) { try { conn.rollback(); } catch (SQLException ex) {} @@ -337,7 +397,8 @@ public class SQLExec extends Task { catch (SQLException e) {} } - log("SQL statements executed successfully", Project.MSG_VERBOSE); + log(goodSql + " of " + totalSql + + " SQL statements executed successfully"); } protected void runStatements(Reader reader) throws SQLException, IOException { @@ -350,9 +411,15 @@ public class SQLExec extends Task { while ((line=in.readLine()) != null){ if (line.trim().startsWith("//")) continue; if (line.trim().startsWith("--")) continue; - + sql += " " + line; sql = sql.trim(); + + // 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 (line.indexOf("--") >= 0) sql += "\n"; + if (sql.endsWith(";")){ log("SQL: " + sql, Project.MSG_VERBOSE); execSQL(sql.substring(0, sql.length()-1)); @@ -365,34 +432,82 @@ public class SQLExec extends Task { execSQL(sql); } }catch(SQLException e){ - log("Failed to execute: " + sql, Project.MSG_ERR); throw e; } } - + /** + * Verify if connected to the correct RDBMS + **/ + protected boolean isValidRdbms(Connection conn) { + if (rdbms == null && version == null) + return true; + + try { + DatabaseMetaData dmd = conn.getMetaData(); + + if (rdbms != null) { + String theVendor = dmd.getDatabaseProductName().toLowerCase(); + + log("RDBMS = " + theVendor, Project.MSG_VERBOSE); + if (theVendor == null || theVendor.indexOf(rdbms) < 0) { + log("Not the required RDBMS: "+rdbms, Project.MSG_VERBOSE); + return false; + } + } + + if (version != null) { + String theVersion = dmd.getDatabaseProductVersion().toLowerCase(); + + log("Version = " + theVersion, Project.MSG_VERBOSE); + if (theVersion == null || + !(theVersion.startsWith(version) || + theVersion.indexOf(" " + version) >= 0)) { + log("Not the required version: \""+ version +"\"", Project.MSG_VERBOSE); + return false; + } + } + } + catch (SQLException e) { + // Could not get the required information + log("Failed to obtain required RDBMS information", Project.MSG_ERR); + return false; + } + + return true; + } + /** * Exec the sql statement. */ protected void execSQL(String sql) throws SQLException { - if (!statement.execute(sql)) { - log(statement.getUpdateCount()+" rows affected", - Project.MSG_VERBOSE); - } - - if (print) { - printResults(); + try { + totalSql++; + if (!statement.execute(sql)) { + log(statement.getUpdateCount()+" rows affected", + Project.MSG_VERBOSE); + } + + if (print) { + printResults(); + } + + SQLWarning warning = conn.getWarnings(); + while(warning!=null){ + log(warning + " sql warning", Project.MSG_VERBOSE); + warning=warning.getNextWarning(); + } + conn.clearWarnings(); + goodSql++; } - - SQLWarning warning = conn.getWarnings(); - while(warning!=null){ - log(warning + " sql warning", Project.MSG_VERBOSE); - warning=warning.getNextWarning(); + catch (SQLException e) { + log("Failed to execute: " + sql, Project.MSG_ERR); + if (!onError.equals("continue")) throw e; + log(e.toString(), Project.MSG_ERR); } - conn.clearWarnings(); } - + /** * print any results in the statement. */ @@ -439,4 +554,47 @@ public class SQLExec extends Task { } } } + + /** + * Enumerated attribute with the values "continue", "stop" and "abort" + * for the onerror attribute. + */ + 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() throws IOException, SQLException { + if (tSqlCommand.length() != 0) { + log("Executing commands", Project.MSG_VERBOSE); + runStatements(new StringReader(tSqlCommand)); + } + + if (tSrcFile != null) { + log("Executing file: " + tSrcFile.getAbsolutePath(), + Project.MSG_VERBOSE); + runStatements(new FileReader(tSrcFile)); + } + } + } + }