You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

SQLExec.java 20 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Ant", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.tools.ant.taskdefs;
  55. import org.apache.tools.ant.BuildException;
  56. import org.apache.tools.ant.DirectoryScanner;
  57. import org.apache.tools.ant.Project;
  58. import org.apache.tools.ant.types.EnumeratedAttribute;
  59. import org.apache.tools.ant.types.FileSet;
  60. import java.io.File;
  61. import java.io.PrintStream;
  62. import java.io.BufferedOutputStream;
  63. import java.io.FileOutputStream;
  64. import java.io.IOException;
  65. import java.io.Reader;
  66. import java.io.BufferedReader;
  67. import java.io.StringReader;
  68. import java.io.FileReader;
  69. import java.io.InputStreamReader;
  70. import java.io.FileInputStream;
  71. import java.util.Enumeration;
  72. import java.util.StringTokenizer;
  73. import java.util.Vector;
  74. import java.sql.Connection;
  75. import java.sql.Statement;
  76. import java.sql.SQLException;
  77. import java.sql.SQLWarning;
  78. import java.sql.ResultSet;
  79. import java.sql.ResultSetMetaData;
  80. /**
  81. * Executes a series of SQL statements on a database using JDBC.
  82. *
  83. * <p>Statements can
  84. * either be read in from a text file using the <i>src</i> attribute or from
  85. * between the enclosing SQL tags.</p>
  86. *
  87. * <p>Multiple statements can be provided, separated by semicolons (or the
  88. * defined <i>delimiter</i>). Individual lines within the statements can be
  89. * commented using either --, // or REM at the start of the line.</p>
  90. *
  91. * <p>The <i>autocommit</i> attribute specifies whether auto-commit should be
  92. * turned on or off whilst executing the statements. If auto-commit is turned
  93. * on each statement will be executed and committed. If it is turned off the
  94. * statements will all be executed as one transaction.</p>
  95. *
  96. * <p>The <i>onerror</i> attribute specifies how to proceed when an error occurs
  97. * during the execution of one of the statements.
  98. * The possible values are: <b>continue</b> execution, only show the error;
  99. * <b>stop</b> execution and commit transaction;
  100. * and <b>abort</b> execution and transaction and fail task.</p>
  101. *
  102. * @author <a href="mailto:jeff@custommonkey.org">Jeff Martin</a>
  103. * @author <A href="mailto:gholam@xtra.co.nz">Michael McCallum</A>
  104. * @author <A href="mailto:tim.stephenson@sybase.com">Tim Stephenson</A>
  105. *
  106. * @since Ant 1.2
  107. *
  108. * @ant.task name="sql" category="database"
  109. */
  110. public class SQLExec extends JDBCTask {
  111. /**
  112. * delimiters we support, "normal" and "row"
  113. */
  114. public static class DelimiterType extends EnumeratedAttribute {
  115. public static final String NORMAL = "normal";
  116. public static final String ROW = "row";
  117. public String[] getValues() {
  118. return new String[] {NORMAL, ROW};
  119. }
  120. }
  121. private int goodSql = 0;
  122. private int totalSql = 0;
  123. /**
  124. * Database connection
  125. */
  126. private Connection conn = null;
  127. /**
  128. * files to load
  129. */
  130. private Vector filesets = new Vector();
  131. /**
  132. * SQL statement
  133. */
  134. private Statement statement = null;
  135. /**
  136. * SQL input file
  137. */
  138. private File srcFile = null;
  139. /**
  140. * SQL input command
  141. */
  142. private String sqlCommand = "";
  143. /**
  144. * SQL transactions to perform
  145. */
  146. private Vector transactions = new Vector();
  147. /**
  148. * SQL Statement delimiter
  149. */
  150. private String delimiter = ";";
  151. /**
  152. * The delimiter type indicating whether the delimiter will
  153. * only be recognized on a line by itself
  154. */
  155. private String delimiterType = DelimiterType.NORMAL;
  156. /**
  157. * Print SQL results.
  158. */
  159. private boolean print = false;
  160. /**
  161. * Print header columns.
  162. */
  163. private boolean showheaders = true;
  164. /**
  165. * Results Output file.
  166. */
  167. private File output = null;
  168. /**
  169. * Action to perform if an error is found
  170. **/
  171. private String onError = "abort";
  172. /**
  173. * Encoding to use when reading SQL statements from a file
  174. */
  175. private String encoding = null;
  176. /**
  177. * Append to an existing file or overwrite it?
  178. */
  179. private boolean append = false;
  180. /**
  181. * Set the name of the SQL file to be run.
  182. * Required unless statements are enclosed in the build file
  183. */
  184. public void setSrc(File srcFile) {
  185. this.srcFile = srcFile;
  186. }
  187. /**
  188. * Set an inline SQL command to execute.
  189. * NB: Properties are not expanded in this text.
  190. */
  191. public void addText(String sql) {
  192. this.sqlCommand += sql;
  193. }
  194. /**
  195. * Adds a set of files (nested fileset attribute).
  196. */
  197. public void addFileset(FileSet set) {
  198. filesets.addElement(set);
  199. }
  200. /**
  201. * Add a SQL transaction to execute
  202. */
  203. public Transaction createTransaction() {
  204. Transaction t = new Transaction();
  205. transactions.addElement(t);
  206. return t;
  207. }
  208. /**
  209. * Set the file encoding to use on the SQL files read in
  210. *
  211. * @param encoding the encoding to use on the files
  212. */
  213. public void setEncoding(String encoding) {
  214. this.encoding = encoding;
  215. }
  216. /**
  217. * Set the delimiter that separates SQL statements;
  218. * optional, default &quot;;&quot;
  219. *
  220. * <p>For example, set this to "go" and delimitertype to "ROW" for
  221. * Sybase ASE or MS SQL Server.</p>
  222. */
  223. public void setDelimiter(String delimiter) {
  224. this.delimiter = delimiter;
  225. }
  226. /**
  227. * Set the delimiter type: "normal" or "row" (default "normal").
  228. *
  229. * <p>The delimiter type takes two values - normal and row. Normal
  230. * means that any occurence of the delimiter terminate the SQL
  231. * command whereas with row, only a line containing just the
  232. * delimiter is recognized as the end of the command.</p>
  233. */
  234. public void setDelimiterType(DelimiterType delimiterType) {
  235. this.delimiterType = delimiterType.getValue();
  236. }
  237. /**
  238. * Print result sets from the statements;
  239. * optional, default false
  240. */
  241. public void setPrint(boolean print) {
  242. this.print = print;
  243. }
  244. /**
  245. * Print headers for result sets from the
  246. * statements; optional, default true.
  247. */
  248. public void setShowheaders(boolean showheaders) {
  249. this.showheaders = showheaders;
  250. }
  251. /**
  252. * Set the output file;
  253. * optional, defaults to the Ant log.
  254. */
  255. public void setOutput(File output) {
  256. this.output = output;
  257. }
  258. /**
  259. * whether output should be appended to or overwrite
  260. * an existing file. Defaults to false.
  261. *
  262. * @since Ant 1.5
  263. */
  264. public void setAppend(boolean append) {
  265. this.append = append;
  266. }
  267. /**
  268. * Action to perform when statement fails: continue, stop, or abort
  269. * optional; default &quot;abort&quot;
  270. */
  271. public void setOnerror(OnError action) {
  272. this.onError = action.getValue();
  273. }
  274. /**
  275. * Load the sql file and then execute it
  276. */
  277. public void execute() throws BuildException {
  278. Vector savedTransaction = (Vector) transactions.clone();
  279. String savedSqlCommand = sqlCommand;
  280. sqlCommand = sqlCommand.trim();
  281. try {
  282. if (srcFile == null && sqlCommand.length() == 0
  283. && filesets.isEmpty()) {
  284. if (transactions.size() == 0) {
  285. throw new BuildException("Source file or fileset, "
  286. + "transactions or sql statement "
  287. + "must be set!", location);
  288. }
  289. }
  290. if (srcFile != null && !srcFile.exists()) {
  291. throw new BuildException("Source file does not exist!", location);
  292. }
  293. // deal with the filesets
  294. for (int i = 0; i < filesets.size(); i++) {
  295. FileSet fs = (FileSet) filesets.elementAt(i);
  296. DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  297. File srcDir = fs.getDir(getProject());
  298. String[] srcFiles = ds.getIncludedFiles();
  299. // Make a transaction for each file
  300. for (int j = 0 ; j < srcFiles.length ; j++) {
  301. Transaction t = createTransaction();
  302. t.setSrc(new File(srcDir, srcFiles[j]));
  303. }
  304. }
  305. // Make a transaction group for the outer command
  306. Transaction t = createTransaction();
  307. t.setSrc(srcFile);
  308. t.addText(sqlCommand);
  309. conn = getConnection();
  310. if (!isValidRdbms(conn)) {
  311. return;
  312. }
  313. try {
  314. statement = conn.createStatement();
  315. PrintStream out = System.out;
  316. try {
  317. if (output != null) {
  318. log("Opening PrintStream to output file " + output,
  319. Project.MSG_VERBOSE);
  320. out = new PrintStream(
  321. new BufferedOutputStream(
  322. new FileOutputStream(output
  323. .getAbsolutePath(),
  324. append)));
  325. }
  326. // Process all transactions
  327. for (Enumeration e = transactions.elements();
  328. e.hasMoreElements();) {
  329. ((Transaction) e.nextElement()).runTransaction(out);
  330. if (!isAutocommit()) {
  331. log("Commiting transaction", Project.MSG_VERBOSE);
  332. conn.commit();
  333. }
  334. }
  335. } finally {
  336. if (out != null && out != System.out) {
  337. out.close();
  338. }
  339. }
  340. } catch (IOException e){
  341. if (!isAutocommit() && conn != null && onError.equals("abort")) {
  342. try {
  343. conn.rollback();
  344. } catch (SQLException ex) {}
  345. }
  346. throw new BuildException(e, location);
  347. } catch (SQLException e){
  348. if (!isAutocommit() && conn != null && onError.equals("abort")) {
  349. try {
  350. conn.rollback();
  351. } catch (SQLException ex) {}
  352. }
  353. throw new BuildException(e, location);
  354. } finally {
  355. try {
  356. if (statement != null) {
  357. statement.close();
  358. }
  359. if (conn != null) {
  360. conn.close();
  361. }
  362. } catch (SQLException e) {}
  363. }
  364. log(goodSql + " of " + totalSql +
  365. " SQL statements executed successfully");
  366. } finally {
  367. transactions = savedTransaction;
  368. sqlCommand = savedSqlCommand;
  369. }
  370. }
  371. /**
  372. * read in lines and execute them
  373. */
  374. protected void runStatements(Reader reader, PrintStream out)
  375. throws SQLException, IOException {
  376. String sql = "";
  377. String line = "";
  378. BufferedReader in = new BufferedReader(reader);
  379. while ((line = in.readLine()) != null){
  380. line = line.trim();
  381. line = getProject().replaceProperties(line);
  382. if (line.startsWith("//")) {
  383. continue;
  384. }
  385. if (line.startsWith("--")) {
  386. continue;
  387. }
  388. StringTokenizer st = new StringTokenizer(line);
  389. if (st.hasMoreTokens()) {
  390. String token = st.nextToken();
  391. if ("REM".equalsIgnoreCase(token)) {
  392. continue;
  393. }
  394. }
  395. sql += " " + line;
  396. sql = sql.trim();
  397. // SQL defines "--" as a comment to EOL
  398. // and in Oracle it may contain a hint
  399. // so we cannot just remove it, instead we must end it
  400. if (line.indexOf("--") >= 0) {
  401. sql += "\n";
  402. }
  403. if ((delimiterType.equals(DelimiterType.NORMAL)
  404. && sql.endsWith(delimiter))
  405. ||
  406. (delimiterType.equals(DelimiterType.ROW)
  407. && line.equals(delimiter))) {
  408. log("SQL: " + sql, Project.MSG_VERBOSE);
  409. execSQL(sql.substring(0, sql.length() - delimiter.length()),
  410. out);
  411. sql = "";
  412. }
  413. }
  414. // Catch any statements not followed by ;
  415. if (!sql.equals("")){
  416. execSQL(sql, out);
  417. }
  418. }
  419. /**
  420. * Exec the sql statement.
  421. */
  422. protected void execSQL(String sql, PrintStream out) throws SQLException {
  423. // Check and ignore empty statements
  424. if ("".equals(sql.trim())) {
  425. return;
  426. }
  427. try {
  428. totalSql++;
  429. if (!statement.execute(sql)) {
  430. log(statement.getUpdateCount() + " rows affected",
  431. Project.MSG_VERBOSE);
  432. } else {
  433. if (print) {
  434. printResults(out);
  435. }
  436. }
  437. SQLWarning warning = conn.getWarnings();
  438. while (warning != null){
  439. log(warning + " sql warning", Project.MSG_VERBOSE);
  440. warning = warning.getNextWarning();
  441. }
  442. conn.clearWarnings();
  443. goodSql++;
  444. } catch (SQLException e) {
  445. log("Failed to execute: " + sql, Project.MSG_ERR);
  446. if (!onError.equals("continue")) {
  447. throw e;
  448. }
  449. log(e.toString(), Project.MSG_ERR);
  450. }
  451. }
  452. /**
  453. * print any results in the statement.
  454. */
  455. protected void printResults(PrintStream out) throws java.sql.SQLException {
  456. ResultSet rs = null;
  457. do {
  458. rs = statement.getResultSet();
  459. if (rs != null) {
  460. log("Processing new result set.", Project.MSG_VERBOSE);
  461. ResultSetMetaData md = rs.getMetaData();
  462. int columnCount = md.getColumnCount();
  463. StringBuffer line = new StringBuffer();
  464. if (showheaders) {
  465. for (int col = 1; col < columnCount; col++) {
  466. line.append(md.getColumnName(col));
  467. line.append(",");
  468. }
  469. line.append(md.getColumnName(columnCount));
  470. out.println(line);
  471. line.setLength(0);
  472. }
  473. while (rs.next()) {
  474. boolean first = true;
  475. for (int col = 1; col <= columnCount; col++) {
  476. String columnValue = rs.getString(col);
  477. if (columnValue != null) {
  478. columnValue = columnValue.trim();
  479. }
  480. if (first) {
  481. first = false;
  482. } else {
  483. line.append(",");
  484. }
  485. line.append(columnValue);
  486. }
  487. out.println(line);
  488. line.setLength(0);
  489. }
  490. }
  491. }
  492. while (statement.getMoreResults());
  493. out.println();
  494. }
  495. /**
  496. * The action a task should perform on an error,
  497. * one of "continue", "stop" and "abort"
  498. */
  499. public static class OnError extends EnumeratedAttribute {
  500. public String[] getValues() {
  501. return new String[] {"continue", "stop", "abort"};
  502. }
  503. }
  504. /**
  505. * Contains the definition of a new transaction element.
  506. * Transactions allow several files or blocks of statements
  507. * to be executed using the same JDBC connection and commit
  508. * operation in between.
  509. */
  510. public class Transaction {
  511. private File tSrcFile = null;
  512. private String tSqlCommand = "";
  513. /**
  514. *
  515. */
  516. public void setSrc(File src) {
  517. this.tSrcFile = src;
  518. }
  519. /**
  520. *
  521. */
  522. public void addText(String sql) {
  523. this.tSqlCommand += sql;
  524. }
  525. /**
  526. *
  527. */
  528. private void runTransaction(PrintStream out)
  529. throws IOException, SQLException {
  530. if (tSqlCommand.length() != 0) {
  531. log("Executing commands", Project.MSG_INFO);
  532. runStatements(new StringReader(tSqlCommand), out);
  533. }
  534. if (tSrcFile != null) {
  535. log("Executing file: " + tSrcFile.getAbsolutePath(),
  536. Project.MSG_INFO);
  537. Reader reader =
  538. (encoding == null) ? new FileReader(tSrcFile)
  539. : new InputStreamReader(
  540. new FileInputStream(tSrcFile),
  541. encoding);
  542. try {
  543. runStatements(reader, out);
  544. } finally {
  545. reader.close();
  546. }
  547. }
  548. }
  549. }
  550. }