View Javadoc
1   /*
2    * Copyright 2004-2025 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.ibatis.common.jdbc;
17  
18  import com.ibatis.common.resources.Resources;
19  
20  import java.io.IOException;
21  import java.io.LineNumberReader;
22  import java.io.PrintWriter;
23  import java.io.Reader;
24  import java.sql.Connection;
25  import java.sql.Driver;
26  import java.sql.DriverManager;
27  import java.sql.ResultSet;
28  import java.sql.ResultSetMetaData;
29  import java.sql.SQLException;
30  import java.sql.Statement;
31  
32  /**
33   * Tool to run database scripts.
34   */
35  public class ScriptRunner {
36  
37    /** The Constant DEFAULT_DELIMITER. */
38    private static final String DEFAULT_DELIMITER = ";";
39  
40    /** The connection. */
41    private Connection connection;
42  
43    /** The driver. */
44    private String driver;
45  
46    /** The url. */
47    private String url;
48  
49    /** The username. */
50    private String username;
51  
52    /** The password. */
53    private String password;
54  
55    /** The stop on error. */
56    private boolean stopOnError;
57  
58    /** The auto commit. */
59    private boolean autoCommit;
60  
61    /** The log writer. */
62    private PrintWriter logWriter = new PrintWriter(System.out);
63  
64    /** The error log writer. */
65    private PrintWriter errorLogWriter = new PrintWriter(System.err);
66  
67    /** The delimiter. */
68    private String delimiter = DEFAULT_DELIMITER;
69  
70    /** The full line delimiter. */
71    private boolean fullLineDelimiter;
72  
73    /**
74     * Default constructor.
75     *
76     * @param connection
77     *          the connection
78     * @param autoCommit
79     *          the auto commit
80     * @param stopOnError
81     *          the stop on error
82     */
83    public ScriptRunner(Connection connection, boolean autoCommit, boolean stopOnError) {
84      this.connection = connection;
85      this.autoCommit = autoCommit;
86      this.stopOnError = stopOnError;
87    }
88  
89    /**
90     * Sets the delimiter.
91     *
92     * @param delimiter
93     *          the delimiter
94     * @param fullLineDelimiter
95     *          the full line delimiter
96     */
97    public void setDelimiter(String delimiter, boolean fullLineDelimiter) {
98      this.delimiter = delimiter;
99      this.fullLineDelimiter = fullLineDelimiter;
100   }
101 
102   /**
103    * Instantiates a new script runner.
104    *
105    * @param driver
106    *          the driver
107    * @param url
108    *          the url
109    * @param username
110    *          the username
111    * @param password
112    *          the password
113    * @param autoCommit
114    *          the auto commit
115    * @param stopOnError
116    *          the stop on error
117    */
118   public ScriptRunner(String driver, String url, String username, String password, boolean autoCommit,
119       boolean stopOnError) {
120     this.driver = driver;
121     this.url = url;
122     this.username = username;
123     this.password = password;
124     this.autoCommit = autoCommit;
125     this.stopOnError = stopOnError;
126   }
127 
128   /**
129    * Setter for logWriter property.
130    *
131    * @param logWriter
132    *          - the new value of the logWriter property
133    */
134   public void setLogWriter(PrintWriter logWriter) {
135     this.logWriter = logWriter;
136   }
137 
138   /**
139    * Setter for errorLogWriter property.
140    *
141    * @param errorLogWriter
142    *          - the new value of the errorLogWriter property
143    */
144   public void setErrorLogWriter(PrintWriter errorLogWriter) {
145     this.errorLogWriter = errorLogWriter;
146   }
147 
148   /**
149    * Runs an SQL script (read in using the Reader parameter).
150    *
151    * @param reader
152    *          - the source of the script
153    *
154    * @throws IOException
155    *           Signals that an I/O exception has occurred.
156    * @throws SQLException
157    *           the SQL exception
158    */
159   public void runScript(Reader reader) throws IOException, SQLException {
160     try {
161       if (connection == null) {
162         DriverManager.registerDriver((Driver) Resources.instantiate(driver));
163         Connection conn = DriverManager.getConnection(url, username, password);
164         try {
165           if (conn.getAutoCommit() != autoCommit) {
166             conn.setAutoCommit(autoCommit);
167           }
168           runScript(conn, reader);
169         } finally {
170           conn.close();
171         }
172       } else {
173         boolean originalAutoCommit = connection.getAutoCommit();
174         try {
175           if (originalAutoCommit != this.autoCommit) {
176             connection.setAutoCommit(this.autoCommit);
177           }
178           runScript(connection, reader);
179         } finally {
180           connection.setAutoCommit(originalAutoCommit);
181         }
182       }
183     } catch (IOException | SQLException e) {
184       throw e;
185     } catch (Exception e) {
186       throw new RuntimeException("Error running script.  Cause: " + e, e);
187     }
188   }
189 
190   /**
191    * Runs an SQL script (read in using the Reader parameter) using the connection passed in.
192    *
193    * @param conn
194    *          - the connection to use for the script
195    * @param reader
196    *          - the source of the script
197    *
198    * @throws IOException
199    *           if there is an error reading from the Reader
200    * @throws SQLException
201    *           if any SQL errors occur
202    */
203   private void runScript(Connection conn, Reader reader) throws IOException, SQLException {
204     StringBuilder command = null;
205     try {
206       LineNumberReader lineReader = new LineNumberReader(reader);
207       String line = null;
208       while ((line = lineReader.readLine()) != null) {
209         if (command == null) {
210           command = new StringBuilder();
211         }
212         String trimmedLine = line.trim();
213         if (trimmedLine.startsWith("--")) {
214           println(trimmedLine);
215         } else if (trimmedLine.isEmpty() || trimmedLine.startsWith("//") || trimmedLine.startsWith("--")) {
216           // Do nothing
217         } else if (!fullLineDelimiter && trimmedLine.endsWith(getDelimiter())
218             || fullLineDelimiter && trimmedLine.equals(getDelimiter())) {
219           command.append(line.substring(0, line.lastIndexOf(getDelimiter())));
220           command.append(" ");
221           Statement statement = conn.createStatement();
222 
223           println(command);
224 
225           boolean hasResults = false;
226           if (stopOnError) {
227             hasResults = statement.execute(command.toString());
228           } else {
229             try {
230               statement.execute(command.toString());
231             } catch (SQLException e) {
232               e.fillInStackTrace();
233               printlnError("Error executing: " + command);
234               printlnError(e);
235             }
236           }
237 
238           if (autoCommit && !conn.getAutoCommit()) {
239             conn.commit();
240           }
241 
242           ResultSet rs = statement.getResultSet();
243           if (hasResults && rs != null) {
244             ResultSetMetaData md = rs.getMetaData();
245             int cols = md.getColumnCount();
246             for (int i = 0; i < cols; i++) {
247               String name = md.getColumnLabel(i + 1);
248               print(name + "\t");
249             }
250             println("");
251             while (rs.next()) {
252               for (int i = 0; i < cols; i++) {
253                 String value = rs.getString(i + 1);
254                 print(value + "\t");
255               }
256               println("");
257             }
258           }
259 
260           command = null;
261           try {
262             statement.close();
263           } catch (Exception e) {
264             // Ignore to workaround a bug in Jakarta DBCP
265           }
266           Thread.yield();
267         } else {
268           command.append(line);
269           command.append(" ");
270         }
271       }
272       if (!autoCommit) {
273         conn.commit();
274       }
275     } catch (IOException | SQLException e) {
276       e.fillInStackTrace();
277       printlnError("Error executing: " + command);
278       printlnError(e);
279       throw e;
280     } finally {
281       conn.rollback();
282       flush();
283     }
284   }
285 
286   /**
287    * Gets the delimiter.
288    *
289    * @return the delimiter
290    */
291   private String getDelimiter() {
292     return delimiter;
293   }
294 
295   /**
296    * Prints the.
297    *
298    * @param o
299    *          the o
300    */
301   private void print(Object o) {
302     if (logWriter != null) {
303       System.out.print(o);
304     }
305   }
306 
307   /**
308    * Println.
309    *
310    * @param o
311    *          the o
312    */
313   private void println(Object o) {
314     if (logWriter != null) {
315       logWriter.println(o);
316     }
317   }
318 
319   /**
320    * Println error.
321    *
322    * @param o
323    *          the o
324    */
325   private void printlnError(Object o) {
326     if (errorLogWriter != null) {
327       errorLogWriter.println(o);
328     }
329   }
330 
331   /**
332    * Flush.
333    */
334   private void flush() {
335     if (logWriter != null) {
336       logWriter.flush();
337     }
338     if (errorLogWriter != null) {
339       errorLogWriter.flush();
340     }
341   }
342 
343 }