ScriptUtils.java 21.0 KB
Newer Older
1
/*
2
 * Copyright 2002-2016 the original author or authors.
3 4 5 6 7
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9 10 11 12 13 14 15 16 17 18 19 20
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.jdbc.datasource.init;

import java.io.IOException;
import java.io.LineNumberReader;
S
Sam Brannen 已提交
21
import java.sql.Connection;
22
import java.sql.SQLException;
23
import java.sql.SQLWarning;
S
Sam Brannen 已提交
24
import java.sql.Statement;
25 26 27 28 29
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
30

31
import org.springframework.core.io.Resource;
32
import org.springframework.core.io.support.EncodedResource;
S
Sam Brannen 已提交
33
import org.springframework.util.Assert;
34 35 36
import org.springframework.util.StringUtils;

/**
37 38 39
 * Generic utility methods for working with SQL scripts.
 *
 * <p>Mainly for internal use within the framework.
S
Sam Brannen 已提交
40 41 42 43 44 45 46 47
 *
 * @author Thomas Risberg
 * @author Sam Brannen
 * @author Juergen Hoeller
 * @author Keith Donald
 * @author Dave Syer
 * @author Chris Beams
 * @author Oliver Gierke
48
 * @author Chris Baldwin
49
 * @author Nicolas Debeissat
50 51 52 53
 * @since 4.0.3
 */
public abstract class ScriptUtils {

S
Sam Brannen 已提交
54
	/**
55
	 * Default statement separator within SQL scripts: {@code ";"}.
S
Sam Brannen 已提交
56 57 58
	 */
	public static final String DEFAULT_STATEMENT_SEPARATOR = ";";

59
	/**
60 61
	 * Fallback statement separator within SQL scripts: {@code "\n"}.
	 * <p>Used if neither a custom separator nor the
62 63 64 65
	 * {@link #DEFAULT_STATEMENT_SEPARATOR} is present in a given script.
	 */
	public static final String FALLBACK_STATEMENT_SEPARATOR = "\n";

66
	/**
67
	 * End of file (EOF) SQL statement separator: {@code "^^^ END OF SCRIPT ^^^"}.
68 69 70 71 72 73 74
	 * <p>This value may be supplied as the {@code separator} to {@link
	 * #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String)}
	 * to denote that an SQL script contains a single statement (potentially
	 * spanning multiple lines) with no explicit statement separator. Note that
	 * such a script should not actually contain this value; it is merely a
	 * <em>virtual</em> statement separator.
	 */
75
	public static final String EOF_STATEMENT_SEPARATOR = "^^^ END OF SCRIPT ^^^";
76

S
Sam Brannen 已提交
77
	/**
78
	 * Default prefix for single-line comments within SQL scripts: {@code "--"}.
S
Sam Brannen 已提交
79
	 */
80 81
	public static final String DEFAULT_COMMENT_PREFIX = "--";

S
Sam Brannen 已提交
82
	/**
83
	 * Default start delimiter for block comments within SQL scripts: {@code "/*"}.
S
Sam Brannen 已提交
84
	 */
85
	public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = "/*";
S
Sam Brannen 已提交
86 87

	/**
88
	 * Default end delimiter for block comments within SQL scripts: <code>"*&#47;"</code>.
S
Sam Brannen 已提交
89
	 */
90 91
	public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/";

S
Sam Brannen 已提交
92

93 94
	private static final Log logger = LogFactory.getLog(ScriptUtils.class);

S
Sam Brannen 已提交
95

96 97
	/**
	 * Split an SQL script into separate statements delimited by the provided
98 99 100 101 102 103 104 105 106 107 108 109 110
	 * separator character. Each individual statement will be added to the
	 * provided {@code List}.
	 * <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
	 * comment prefix; any text beginning with the comment prefix and extending to
	 * the end of the line will be omitted from the output. Similarly,
	 * {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
	 * {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
	 * <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
	 * in a block comment will be omitted from the output. In addition, multiple
	 * adjacent whitespace characters will be collapsed into a single space.
	 * @param script the SQL script
	 * @param separator character separating each statement &mdash; typically a ';'
	 * @param statements the list that will contain the individual statements
111
	 * @throws ScriptException if an error occurred while splitting the SQL script
112 113 114 115 116 117 118 119 120 121
	 * @see #splitSqlScript(String, String, List)
	 * @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
	 */
	public static void splitSqlScript(String script, char separator, List<String> statements) throws ScriptException {
		splitSqlScript(script, String.valueOf(separator), statements);
	}

	/**
	 * Split an SQL script into separate statements delimited by the provided
	 * separator string. Each individual statement will be added to the
122
	 * provided {@code List}.
S
Sam Brannen 已提交
123 124 125 126 127 128 129 130
	 * <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
	 * comment prefix; any text beginning with the comment prefix and extending to
	 * the end of the line will be omitted from the output. Similarly,
	 * {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
	 * {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
	 * <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
	 * in a block comment will be omitted from the output. In addition, multiple
	 * adjacent whitespace characters will be collapsed into a single space.
131
	 * @param script the SQL script
132
	 * @param separator text separating each statement &mdash; typically a ';' or newline character
133
	 * @param statements the list that will contain the individual statements
134
	 * @throws ScriptException if an error occurred while splitting the SQL script
135
	 * @see #splitSqlScript(String, char, List)
S
Sam Brannen 已提交
136
	 * @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
137
	 */
138 139
	public static void splitSqlScript(String script, String separator, List<String> statements) throws ScriptException {
		splitSqlScript(null, script, separator, DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
140
				DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
141 142 143 144
	}

	/**
	 * Split an SQL script into separate statements delimited by the provided
145
	 * separator string. Each individual statement will be added to the provided
146
	 * {@code List}.
S
Sam Brannen 已提交
147
	 * <p>Within the script, the provided {@code commentPrefix} will be honored:
148
	 * any text beginning with the comment prefix and extending to the end of the
S
Sam Brannen 已提交
149 150 151 152 153 154 155
	 * line will be omitted from the output. Similarly, the provided
	 * {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
	 * delimiters will be honored: any text enclosed in a block comment will be
	 * omitted from the output. In addition, multiple adjacent whitespace characters
	 * will be collapsed into a single space.
	 * @param resource the resource from which the script was read
	 * @param script the SQL script; never {@code null} or empty
156 157
	 * @param separator text separating each statement &mdash; typically a ';' or
	 * newline character; never {@code null}
S
Sam Brannen 已提交
158 159 160 161 162 163 164
	 * @param commentPrefix the prefix that identifies SQL line comments &mdash;
	 * typically "--"; never {@code null} or empty
	 * @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
	 * never {@code null} or empty
	 * @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
	 * never {@code null} or empty
	 * @param statements the list that will contain the individual statements
165
	 * @throws ScriptException if an error occurred while splitting the SQL script
166
	 */
167
	public static void splitSqlScript(EncodedResource resource, String script, String separator, String commentPrefix,
S
Sam Brannen 已提交
168 169 170 171
			String blockCommentStartDelimiter, String blockCommentEndDelimiter, List<String> statements)
			throws ScriptException {

		Assert.hasText(script, "script must not be null or empty");
172
		Assert.notNull(separator, "separator must not be null");
S
Sam Brannen 已提交
173 174 175 176
		Assert.hasText(commentPrefix, "commentPrefix must not be null or empty");
		Assert.hasText(blockCommentStartDelimiter, "blockCommentStartDelimiter must not be null or empty");
		Assert.hasText(blockCommentEndDelimiter, "blockCommentEndDelimiter must not be null or empty");

177
		StringBuilder sb = new StringBuilder();
178 179
		boolean inSingleQuote = false;
		boolean inDoubleQuote = false;
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
		boolean inEscape = false;
		char[] content = script.toCharArray();
		for (int i = 0; i < script.length(); i++) {
			char c = content[i];
			if (inEscape) {
				inEscape = false;
				sb.append(c);
				continue;
			}
			// MySQL style escapes
			if (c == '\\') {
				inEscape = true;
				sb.append(c);
				continue;
			}
195 196
			if (!inDoubleQuote && (c == '\'')) {
				inSingleQuote = !inSingleQuote;
197
			}
198 199 200 201
			else if (!inSingleQuote && (c == '"')) {
				inDoubleQuote = !inDoubleQuote;
			}
			if (!inSingleQuote && !inDoubleQuote) {
202
				if (script.startsWith(separator, i)) {
203 204 205 206 207
					// we've reached the end of the current statement
					if (sb.length() > 0) {
						statements.add(sb.toString());
						sb = new StringBuilder();
					}
208
					i += separator.length() - 1;
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
					continue;
				}
				else if (script.startsWith(commentPrefix, i)) {
					// skip over any content from the start of the comment to the EOL
					int indexOfNextNewline = script.indexOf("\n", i);
					if (indexOfNextNewline > i) {
						i = indexOfNextNewline;
						continue;
					}
					else {
						// if there's no EOL, we must be at the end
						// of the script, so stop here.
						break;
					}
				}
S
Sam Brannen 已提交
224
				else if (script.startsWith(blockCommentStartDelimiter, i)) {
225
					// skip over any block comments
S
Sam Brannen 已提交
226 227 228
					int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
					if (indexOfCommentEnd > i) {
						i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
229 230 231
						continue;
					}
					else {
S
Sam Brannen 已提交
232 233
						throw new ScriptParseException(String.format("Missing block comment end delimiter [%s].",
							blockCommentEndDelimiter), resource);
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
					}
				}
				else if (c == ' ' || c == '\n' || c == '\t') {
					// avoid multiple adjacent whitespace characters
					if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
						c = ' ';
					}
					else {
						continue;
					}
				}
			}
			sb.append(c);
		}
		if (StringUtils.hasText(sb)) {
			statements.add(sb.toString());
		}
	}

	/**
S
Sam Brannen 已提交
254 255
	 * Read a script from the given resource, using "{@code --}" as the comment prefix
	 * and "{@code ;}" as the statement separator, and build a String containing the lines.
256 257 258 259
	 * @param resource the {@code EncodedResource} to be read
	 * @return {@code String} containing the script lines
	 * @throws IOException in case of I/O errors
	 */
S
Sam Brannen 已提交
260 261
	static String readScript(EncodedResource resource) throws IOException {
		return readScript(resource, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR);
262
	}
S
Sam Brannen 已提交
263

264
	/**
265 266
	 * Read a script from the provided resource, using the supplied comment prefix
	 * and statement separator, and build a {@code String} containing the lines.
267 268 269 270 271
	 * <p>Lines <em>beginning</em> with the comment prefix are excluded from the
	 * results; however, line comments anywhere else &mdash; for example, within
	 * a statement &mdash; will be included in the results.
	 * @param resource the {@code EncodedResource} containing the script
	 * to be processed
S
Sam Brannen 已提交
272 273
	 * @param commentPrefix the prefix that identifies comments in the SQL script &mdash;
	 * typically "--"
274 275
	 * @param separator the statement separator in the SQL script &mdash; typically ";"
	 * @return a {@code String} containing the script lines
276
	 * @throws IOException in case of I/O errors
277
	 */
S
Sam Brannen 已提交
278 279
	private static String readScript(EncodedResource resource, String commentPrefix, String separator)
			throws IOException {
280

281 282 283 284 285 286 287 288
		LineNumberReader lnr = new LineNumberReader(resource.getReader());
		try {
			return readScript(lnr, commentPrefix, separator);
		}
		finally {
			lnr.close();
		}
	}
S
Sam Brannen 已提交
289

290 291
	/**
	 * Read a script from the provided {@code LineNumberReader}, using the supplied
S
Sam Brannen 已提交
292 293
	 * comment prefix and statement separator, and build a {@code String} containing
	 * the lines.
294 295 296 297 298
	 * <p>Lines <em>beginning</em> with the comment prefix are excluded from the
	 * results; however, line comments anywhere else &mdash; for example, within
	 * a statement &mdash; will be included in the results.
	 * @param lineNumberReader the {@code LineNumberReader} containing the script
	 * to be processed
S
Sam Brannen 已提交
299 300
	 * @param commentPrefix the prefix that identifies comments in the SQL script &mdash;
	 * typically "--"
301 302
	 * @param separator the statement separator in the SQL script &mdash; typically ";"
	 * @return a {@code String} containing the script lines
303
	 * @throws IOException in case of I/O errors
304
	 */
S
Sam Brannen 已提交
305 306
	public static String readScript(LineNumberReader lineNumberReader, String commentPrefix, String separator)
			throws IOException {
307

308 309 310
		String currentStatement = lineNumberReader.readLine();
		StringBuilder scriptBuilder = new StringBuilder();
		while (currentStatement != null) {
311
			if (commentPrefix != null && !currentStatement.startsWith(commentPrefix)) {
312 313 314 315 316 317 318
				if (scriptBuilder.length() > 0) {
					scriptBuilder.append('\n');
				}
				scriptBuilder.append(currentStatement);
			}
			currentStatement = lineNumberReader.readLine();
		}
319
		appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
320 321 322
		return scriptBuilder.toString();
	}

323
	private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, String separator) {
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
		if (separator == null) {
			return;
		}
		String trimmed = separator.trim();
		if (trimmed.length() == separator.length()) {
			return;
		}
		// separator ends in whitespace, so we might want to see if the script is trying
		// to end the same way
		if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
			scriptBuilder.append(separator.substring(trimmed.length()));
		}
	}

	/**
	 * Does the provided SQL script contain the specified delimiter?
	 * @param script the SQL script
	 * @param delim String delimiting each statement - typically a ';' character
	 */
	public static boolean containsSqlScriptDelimiters(String script, String delim) {
		boolean inLiteral = false;
		char[] content = script.toCharArray();
		for (int i = 0; i < script.length(); i++) {
			if (content[i] == '\'') {
				inLiteral = !inLiteral;
			}
			if (!inLiteral && script.startsWith(delim, i)) {
				return true;
			}
		}
		return false;
	}

357
	/**
358 359
	 * Execute the given SQL script using default settings for statement
	 * separators, comment delimiters, and exception handling flags.
360 361
	 * <p>Statement separators and comments will be removed before executing
	 * individual statements within the supplied script.
362 363
	 * <p><strong>Warning</strong>: this method does <em>not</em> release the
	 * provided {@link Connection}.
364 365 366 367
	 * @param connection the JDBC connection to use to execute the script; already
	 * configured and ready to use
	 * @param resource the resource to load the SQL script from; encoded with the
	 * current platform's default encoding
368
	 * @throws ScriptException if an error occurred while executing the SQL script
369 370
	 * @see #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String)
	 * @see #DEFAULT_STATEMENT_SEPARATOR
371
	 * @see #DEFAULT_COMMENT_PREFIX
372 373
	 * @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
	 * @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
374 375
	 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
	 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
376
	 */
377
	public static void executeSqlScript(Connection connection, Resource resource) throws ScriptException {
378 379 380 381
		executeSqlScript(connection, new EncodedResource(resource));
	}

	/**
382 383
	 * Execute the given SQL script using default settings for statement
	 * separators, comment delimiters, and exception handling flags.
384 385
	 * <p>Statement separators and comments will be removed before executing
	 * individual statements within the supplied script.
386 387
	 * <p><strong>Warning</strong>: this method does <em>not</em> release the
	 * provided {@link Connection}.
388 389 390 391
	 * @param connection the JDBC connection to use to execute the script; already
	 * configured and ready to use
	 * @param resource the resource (potentially associated with a specific encoding)
	 * to load the SQL script from
392
	 * @throws ScriptException if an error occurred while executing the SQL script
393 394
	 * @see #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String)
	 * @see #DEFAULT_STATEMENT_SEPARATOR
395
	 * @see #DEFAULT_COMMENT_PREFIX
396 397
	 * @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
	 * @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
398 399
	 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
	 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
400
	 */
401
	public static void executeSqlScript(Connection connection, EncodedResource resource) throws ScriptException {
402
		executeSqlScript(connection, resource, false, false, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR,
403
				DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
404 405
	}

406 407
	/**
	 * Execute the given SQL script.
S
Sam Brannen 已提交
408 409
	 * <p>Statement separators and comments will be removed before executing
	 * individual statements within the supplied script.
410 411
	 * <p><strong>Warning</strong>: this method does <em>not</em> release the
	 * provided {@link Connection}.
S
Sam Brannen 已提交
412 413 414 415 416 417 418 419
	 * @param connection the JDBC connection to use to execute the script; already
	 * configured and ready to use
	 * @param resource the resource (potentially associated with a specific encoding)
	 * to load the SQL script from
	 * @param continueOnError whether or not to continue without throwing an exception
	 * in the event of an error
	 * @param ignoreFailedDrops whether or not to continue in the event of specifically
	 * an error on a {@code DROP} statement
420 421
	 * @param commentPrefix the prefix that identifies single-line comments in the
	 * SQL script &mdash; typically "--"
S
Sam Brannen 已提交
422
	 * @param separator the script statement separator; defaults to
423
	 * {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to
424 425 426
	 * {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to
	 * {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a
	 * single statement without a separator
S
Sam Brannen 已提交
427 428 429 430
	 * @param blockCommentStartDelimiter the <em>start</em> block comment delimiter; never
	 * {@code null} or empty
	 * @param blockCommentEndDelimiter the <em>end</em> block comment delimiter; never
	 * {@code null} or empty
431
	 * @throws ScriptException if an error occurred while executing the SQL script
432 433 434
	 * @see #DEFAULT_STATEMENT_SEPARATOR
	 * @see #FALLBACK_STATEMENT_SEPARATOR
	 * @see #EOF_STATEMENT_SEPARATOR
435 436
	 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
	 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
437
	 */
S
Sam Brannen 已提交
438 439
	public static void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
			boolean ignoreFailedDrops, String commentPrefix, String separator, String blockCommentStartDelimiter,
440
			String blockCommentEndDelimiter) throws ScriptException {
441 442

		try {
443 444 445 446
			if (logger.isInfoEnabled()) {
				logger.info("Executing SQL script from " + resource);
			}
			long startTime = System.currentTimeMillis();
447

448 449 450 451 452 453 454
			String script;
			try {
				script = readScript(resource, commentPrefix, separator);
			}
			catch (IOException ex) {
				throw new CannotReadScriptException(resource, ex);
			}
S
Sam Brannen 已提交
455

456 457 458
			if (separator == null) {
				separator = DEFAULT_STATEMENT_SEPARATOR;
			}
459
			if (!EOF_STATEMENT_SEPARATOR.equals(separator) && !containsSqlScriptDelimiters(script, separator)) {
460 461 462
				separator = FALLBACK_STATEMENT_SEPARATOR;
			}

463
			List<String> statements = new LinkedList<String>();
464
			splitSqlScript(resource, script, separator, commentPrefix, blockCommentStartDelimiter,
465 466 467
					blockCommentEndDelimiter, statements);

			int stmtNumber = 0;
468 469 470
			Statement stmt = connection.createStatement();
			try {
				for (String statement : statements) {
471
					stmtNumber++;
472 473 474
					try {
						stmt.execute(statement);
						int rowsAffected = stmt.getUpdateCount();
S
Sam Brannen 已提交
475
						if (logger.isDebugEnabled()) {
476 477 478 479 480 481 482 483
							logger.debug(rowsAffected + " returned as update count for SQL: " + statement);
							SQLWarning warningToLog = stmt.getWarnings();
							while (warningToLog != null) {
								logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() +
										"', error code '" + warningToLog.getErrorCode() +
										"', message [" + warningToLog.getMessage() + "]");
								warningToLog = warningToLog.getNextWarning();
							}
S
Sam Brannen 已提交
484 485
						}
					}
486 487 488 489
					catch (SQLException ex) {
						boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
						if (continueOnError || (dropStatement && ignoreFailedDrops)) {
							if (logger.isDebugEnabled()) {
490
								logger.debug(ScriptStatementFailedException.buildErrorMessage(statement, stmtNumber, resource), ex);
491 492 493
							}
						}
						else {
494
							throw new ScriptStatementFailedException(statement, stmtNumber, resource, ex);
495
						}
S
Sam Brannen 已提交
496
					}
497 498
				}
			}
499 500 501 502 503 504 505
			finally {
				try {
					stmt.close();
				}
				catch (Throwable ex) {
					logger.debug("Could not close JDBC Statement", ex);
				}
S
Sam Brannen 已提交
506
			}
507 508 509 510

			long elapsedTime = System.currentTimeMillis() - startTime;
			if (logger.isInfoEnabled()) {
				logger.info("Executed SQL script from " + resource + " in " + elapsedTime + " ms.");
S
Sam Brannen 已提交
511 512
			}
		}
513 514 515 516 517 518
		catch (Exception ex) {
			if (ex instanceof ScriptException) {
				throw (ScriptException) ex;
			}
			throw new UncategorizedScriptException(
				"Failed to execute database script from resource [" + resource + "]", ex);
519 520 521 522
		}
	}

}