/* * Copyright 2002-2014 the original author or authors. * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.io.support.EncodedResource; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.util.StringUtils; /** * Generic utility methods for working with SQL scripts. Mainly for * internal use within the framework. * * @author Chris Baldwin * @since 4.0.3 */ public abstract class ScriptUtils { public static final String DEFAULT_COMMENT_PREFIX = "--"; public static final char DEFAULT_STATEMENT_SEPARATOR_CHAR = ';'; public static final String DEFAULT_STATEMENT_SEPARATOR = String.valueOf(DEFAULT_STATEMENT_SEPARATOR_CHAR); public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = "/*"; public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/"; private static final Log logger = LogFactory.getLog(ScriptUtils.class); /** * Split an SQL script into separate statements delimited by the provided * delimiter character. Each individual statement will be added to the * provided {@code List}. *
Within a statement, "{@code --}" 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 statement. In addition, multiple adjacent
* whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param delim character delimiting each statement — typically a ';' character
* @param statements the list that will contain the individual statements
* @since 4.0.3
*/
public static void splitSqlScript(String script, char delim, List Within a statement, the provided {@code commentPrefix} will be honored;
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the statement. In addition, multiple adjacent
* whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param delim character delimiting each statement — typically a ';' character
* @param commentPrefix the prefix that identifies line comments in the SQL script — typically "--"
* @param statements the List that will contain the individual statements
* @since 4.0.3
*/
public static void splitSqlScript(String script, String delim, String commentPrefix, String blockCommentOpen,
String blockCommentClose, List Lines beginning with the comment prefix are excluded from the
* results; however, line comments anywhere else — for example, within
* a statement — will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script — typically "--"
* @param separator the statement separator in the SQL script — typically ";"
* @return a {@code String} containing the script lines
* @since 4.0.3
*/
public static String readScript(EncodedResource resource, String commentPrefix,
String separator) throws IOException {
LineNumberReader lnr = new LineNumberReader(resource.getReader());
try {
return readScript(lnr, commentPrefix, separator);
}
finally {
lnr.close();
}
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing the lines.
* Lines beginning with the comment prefix are excluded from the
* results; however, line comments anywhere else — for example, within
* a statement — will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script — typically "--"
* @param separator the statement separator in the SQL script — typically ";"
* @return a {@code String} containing the script lines
* @since 4.0.3
*/
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix,
String separator) throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if (StringUtils.hasText(currentStatement) &&
(commentPrefix != null && !currentStatement.startsWith(commentPrefix))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentStatement);
}
currentStatement = lineNumberReader.readLine();
}
maybeAddSeparatorToScript(scriptBuilder, separator);
return scriptBuilder.toString();
}
private static void maybeAddSeparatorToScript(StringBuilder scriptBuilder, String separator) {
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 character delimiting each statement - typically a ';' character
* @since 4.0.3
*/
public static boolean containsSqlScriptDelimiters(String script, char delim) {
return containsSqlScriptDelimiters(script, String.valueOf(delim));
}
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim String delimiting each statement - typically a ';' character
* @since 4.0.3
*/
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;
}
/**
* Execute the given SQL script.
* The script will normally be loaded by classpath. There should be one statement
* per line. Any statement separators will be removed.
* Do not use this method to execute DDL if you expect rollback.
* @param executor the {@code ScriptStatementExecutor} with which to perform JDBC operations
* @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 of not to continue in the event of specifically an error on a {@code DROP}
* @param commentPrefix the script line comment prefix
* if not specified
* @param separator the script statement separator, defaults to {@code DEFAUT_STATEMENT_SEPARATOR}
* if not specified
* @param blockCommentStartDelim the script block comment starting delimiter
* @param blockCommentEndDelim the script block comment ending delimiter
* @since 4.0.3
*/
public static void executeSqlScript(ScriptStatementExecutor executor, EncodedResource resource,
boolean continueOnError, boolean ignoreFailedDrops, String commentPrefix, String separator,
String blockCommentStartDelim, String blockCommentEndDelim) throws DataAccessException {
if (logger.isInfoEnabled()) {
logger.info("Executing SQL script from " + resource);
}
long startTime = System.currentTimeMillis();
List