parent
fab7b1cebe
commit
4c642ccdcd
|
|
@ -35,7 +35,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic utility methods for working with SQL scripts.
|
* Generic utility methods for working with SQL scripts in conjunction with JDBC.
|
||||||
*
|
*
|
||||||
* <p>Mainly for internal use within the framework.
|
* <p>Mainly for internal use within the framework.
|
||||||
*
|
*
|
||||||
|
|
@ -50,6 +50,7 @@ import org.springframework.util.StringUtils;
|
||||||
* @author Nicolas Debeissat
|
* @author Nicolas Debeissat
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 4.0.3
|
* @since 4.0.3
|
||||||
|
* @see org.springframework.r2dbc.connection.init.ScriptUtils
|
||||||
*/
|
*/
|
||||||
public abstract class ScriptUtils {
|
public abstract class ScriptUtils {
|
||||||
|
|
||||||
|
|
@ -101,405 +102,6 @@ public abstract class ScriptUtils {
|
||||||
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
|
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split an SQL script into separate statements delimited by the provided
|
|
||||||
* 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 (typically a ';')
|
|
||||||
* @param statements the list that will contain the individual statements
|
|
||||||
* @throws ScriptException if an error occurred while splitting the SQL script
|
|
||||||
* @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
|
|
||||||
* 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 text separating each statement
|
|
||||||
* (typically a ';' or newline character)
|
|
||||||
* @param statements the list that will contain the individual statements
|
|
||||||
* @throws ScriptException if an error occurred while splitting the SQL script
|
|
||||||
* @see #splitSqlScript(String, char, List)
|
|
||||||
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
|
|
||||||
*/
|
|
||||||
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,
|
|
||||||
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split an SQL script into separate statements delimited by the provided
|
|
||||||
* separator string. Each individual statement will be added to the provided
|
|
||||||
* {@code List}.
|
|
||||||
* <p>Within the script, 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 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
|
|
||||||
* @param separator text separating each statement
|
|
||||||
* (typically a ';' or newline character)
|
|
||||||
* @param commentPrefix the prefix that identifies SQL line comments
|
|
||||||
* (typically "--")
|
|
||||||
* @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
|
|
||||||
* @throws ScriptException if an error occurred while splitting the SQL script
|
|
||||||
*/
|
|
||||||
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
|
|
||||||
String separator, String commentPrefix, String blockCommentStartDelimiter,
|
|
||||||
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
|
|
||||||
|
|
||||||
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
|
|
||||||
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
|
|
||||||
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split an SQL script into separate statements delimited by the provided
|
|
||||||
* separator string. Each individual statement will be added to the provided
|
|
||||||
* {@code List}.
|
|
||||||
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
|
|
||||||
* any text beginning with one of the comment prefixes and extending to the
|
|
||||||
* end of the 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
|
|
||||||
* @param separator text separating each statement
|
|
||||||
* (typically a ';' or newline character)
|
|
||||||
* @param commentPrefixes the prefixes that identify SQL line comments
|
|
||||||
* (typically "--")
|
|
||||||
* @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
|
|
||||||
* @throws ScriptException if an error occurred while splitting the SQL script
|
|
||||||
* @since 5.2
|
|
||||||
*/
|
|
||||||
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
|
|
||||||
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
|
||||||
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
|
|
||||||
|
|
||||||
Assert.hasText(script, "'script' must not be null or empty");
|
|
||||||
Assert.notNull(separator, "'separator' must not be null");
|
|
||||||
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
|
|
||||||
for (String commentPrefix : commentPrefixes) {
|
|
||||||
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
|
|
||||||
}
|
|
||||||
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
|
|
||||||
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
boolean inSingleQuote = false;
|
|
||||||
boolean inDoubleQuote = false;
|
|
||||||
boolean inEscape = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < script.length(); i++) {
|
|
||||||
char c = script.charAt(i);
|
|
||||||
if (inEscape) {
|
|
||||||
inEscape = false;
|
|
||||||
sb.append(c);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// MySQL style escapes
|
|
||||||
if (c == '\\') {
|
|
||||||
inEscape = true;
|
|
||||||
sb.append(c);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!inDoubleQuote && (c == '\'')) {
|
|
||||||
inSingleQuote = !inSingleQuote;
|
|
||||||
}
|
|
||||||
else if (!inSingleQuote && (c == '"')) {
|
|
||||||
inDoubleQuote = !inDoubleQuote;
|
|
||||||
}
|
|
||||||
if (!inSingleQuote && !inDoubleQuote) {
|
|
||||||
if (script.startsWith(separator, i)) {
|
|
||||||
// We've reached the end of the current statement
|
|
||||||
if (sb.length() > 0) {
|
|
||||||
statements.add(sb.toString());
|
|
||||||
sb = new StringBuilder();
|
|
||||||
}
|
|
||||||
i += separator.length() - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (startsWithAny(script, commentPrefixes, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
|
||||||
// Skip over any block comments
|
|
||||||
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
|
||||||
if (indexOfCommentEnd > i) {
|
|
||||||
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new ScriptParseException(
|
|
||||||
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == ' ' || c == '\r' || 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a script from the provided resource, using the supplied comment prefixes
|
|
||||||
* and statement separator, and build a {@code String} containing the lines.
|
|
||||||
* <p>Lines <em>beginning</em> with one of the comment prefixes 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 separator the statement separator in the SQL script (typically ";")
|
|
||||||
* @param commentPrefixes the prefixes that identify comments in the SQL script
|
|
||||||
* (typically "--")
|
|
||||||
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
|
||||||
* @return a {@code String} containing the script lines
|
|
||||||
* @throws IOException in case of I/O errors
|
|
||||||
*/
|
|
||||||
static String readScript(EncodedResource resource, @Nullable String separator,
|
|
||||||
@Nullable String[] commentPrefixes, @Nullable String blockCommentEndDelimiter) throws IOException {
|
|
||||||
|
|
||||||
try (LineNumberReader lnr = new LineNumberReader(resource.getReader())) {
|
|
||||||
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a script from the provided {@code LineNumberReader}, using the supplied
|
|
||||||
* comment prefix and statement separator, and build a {@code String} containing
|
|
||||||
* the lines.
|
|
||||||
* <p>Lines <em>beginning</em> 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 ";")
|
|
||||||
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
|
||||||
* @return a {@code String} containing the script lines
|
|
||||||
* @throws IOException in case of I/O errors
|
|
||||||
*/
|
|
||||||
public static String readScript(LineNumberReader lineNumberReader, @Nullable String commentPrefix,
|
|
||||||
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
|
|
||||||
|
|
||||||
String[] commentPrefixes = (commentPrefix != null) ? new String[] { commentPrefix } : null;
|
|
||||||
return readScript(lineNumberReader, commentPrefixes, separator, blockCommentEndDelimiter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a script from the provided {@code LineNumberReader}, using the supplied
|
|
||||||
* comment prefixes and statement separator, and build a {@code String} containing
|
|
||||||
* the lines.
|
|
||||||
* <p>Lines <em>beginning</em> with one of the comment prefixes 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 commentPrefixes the prefixes that identify comments in the SQL script
|
|
||||||
* (typically "--")
|
|
||||||
* @param separator the statement separator in the SQL script (typically ";")
|
|
||||||
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
|
||||||
* @return a {@code String} containing the script lines
|
|
||||||
* @throws IOException in case of I/O errors
|
|
||||||
* @since 5.2
|
|
||||||
*/
|
|
||||||
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] commentPrefixes,
|
|
||||||
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
|
|
||||||
|
|
||||||
String currentStatement = lineNumberReader.readLine();
|
|
||||||
StringBuilder scriptBuilder = new StringBuilder();
|
|
||||||
while (currentStatement != null) {
|
|
||||||
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
|
|
||||||
(commentPrefixes != null && !startsWithAny(currentStatement, commentPrefixes, 0))) {
|
|
||||||
if (scriptBuilder.length() > 0) {
|
|
||||||
scriptBuilder.append('\n');
|
|
||||||
}
|
|
||||||
scriptBuilder.append(currentStatement);
|
|
||||||
}
|
|
||||||
currentStatement = lineNumberReader.readLine();
|
|
||||||
}
|
|
||||||
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
|
|
||||||
return scriptBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable 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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
|
|
||||||
for (String prefix : prefixes) {
|
|
||||||
if (script.startsWith(prefix, offset)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the provided SQL script contains the specified delimiter.
|
|
||||||
* <p>This method is intended to be used to find the string delimiting each
|
|
||||||
* SQL statement — for example, a ';' character.
|
|
||||||
* <p>Any occurrence of the delimiter within the script will be ignored if it
|
|
||||||
* is within a <em>literal</em> block of text enclosed in single quotes
|
|
||||||
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
|
|
||||||
* ({@code \}), or if it is within a single-line comment or block comment.
|
|
||||||
* @param script the SQL script to search within
|
|
||||||
* @param delimiter the statement delimiter to search for
|
|
||||||
* @see #DEFAULT_COMMENT_PREFIXES
|
|
||||||
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
|
|
||||||
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
|
|
||||||
*/
|
|
||||||
public static boolean containsSqlScriptDelimiters(String script, String delimiter) {
|
|
||||||
return containsStatementSeparator(null, script, delimiter, DEFAULT_COMMENT_PREFIXES,
|
|
||||||
DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the provided SQL script contains the specified statement separator.
|
|
||||||
* <p>This method is intended to be used to find the string separating each
|
|
||||||
* SQL statement — for example, a ';' character.
|
|
||||||
* <p>Any occurrence of the separator within the script will be ignored if it
|
|
||||||
* is within a <em>literal</em> block of text enclosed in single quotes
|
|
||||||
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
|
|
||||||
* ({@code \}), or if it is within a single-line comment or block comment.
|
|
||||||
* @param resource the resource from which the script was read, or {@code null}
|
|
||||||
* if unknown
|
|
||||||
* @param script the SQL script to search within
|
|
||||||
* @param separator the statement separator to search for
|
|
||||||
* @param commentPrefixes the prefixes that identify single-line comments
|
|
||||||
* (typically {@code "--"})
|
|
||||||
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
|
|
||||||
* (typically {@code "/*"})
|
|
||||||
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
|
||||||
* (typically <code>"*/"</code>)
|
|
||||||
* @since 5.2.16
|
|
||||||
*/
|
|
||||||
private static boolean containsStatementSeparator(@Nullable EncodedResource resource, String script,
|
|
||||||
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
|
||||||
String blockCommentEndDelimiter) throws ScriptException {
|
|
||||||
|
|
||||||
boolean inSingleQuote = false;
|
|
||||||
boolean inDoubleQuote = false;
|
|
||||||
boolean inEscape = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < script.length(); i++) {
|
|
||||||
char c = script.charAt(i);
|
|
||||||
if (inEscape) {
|
|
||||||
inEscape = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// MySQL style escapes
|
|
||||||
if (c == '\\') {
|
|
||||||
inEscape = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!inDoubleQuote && (c == '\'')) {
|
|
||||||
inSingleQuote = !inSingleQuote;
|
|
||||||
}
|
|
||||||
else if (!inSingleQuote && (c == '"')) {
|
|
||||||
inDoubleQuote = !inDoubleQuote;
|
|
||||||
}
|
|
||||||
if (!inSingleQuote && !inDoubleQuote) {
|
|
||||||
if (script.startsWith(separator, i)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (startsWithAny(script, commentPrefixes, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
|
||||||
// Skip over any block comments
|
|
||||||
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
|
||||||
if (indexOfCommentEnd > i) {
|
|
||||||
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new ScriptParseException(
|
|
||||||
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the given SQL script using default settings for statement
|
* Execute the given SQL script using default settings for statement
|
||||||
* separators, comment delimiters, and exception handling flags.
|
* separators, comment delimiters, and exception handling flags.
|
||||||
|
|
@ -705,4 +307,403 @@ public abstract class ScriptUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided resource, using the supplied comment prefixes
|
||||||
|
* and statement separator, and build a {@code String} containing the lines.
|
||||||
|
* <p>Lines <em>beginning</em> with one of the comment prefixes 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 separator the statement separator in the SQL script (typically ";")
|
||||||
|
* @param commentPrefixes the prefixes that identify comments in the SQL script
|
||||||
|
* (typically "--")
|
||||||
|
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
||||||
|
* @return a {@code String} containing the script lines
|
||||||
|
* @throws IOException in case of I/O errors
|
||||||
|
*/
|
||||||
|
static String readScript(EncodedResource resource, @Nullable String separator,
|
||||||
|
String[] commentPrefixes, String blockCommentEndDelimiter) throws IOException {
|
||||||
|
|
||||||
|
try (LineNumberReader lnr = new LineNumberReader(resource.getReader())) {
|
||||||
|
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided {@code LineNumberReader}, using the supplied
|
||||||
|
* comment prefix and statement separator, and build a {@code String} containing
|
||||||
|
* the lines.
|
||||||
|
* <p>Lines <em>beginning</em> 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 ";")
|
||||||
|
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
||||||
|
* @return a {@code String} containing the script lines
|
||||||
|
* @throws IOException in case of I/O errors
|
||||||
|
*/
|
||||||
|
public static String readScript(LineNumberReader lineNumberReader, @Nullable String commentPrefix,
|
||||||
|
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
|
||||||
|
|
||||||
|
String[] commentPrefixes = (commentPrefix != null) ? new String[] { commentPrefix } : null;
|
||||||
|
return readScript(lineNumberReader, commentPrefixes, separator, blockCommentEndDelimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided {@code LineNumberReader}, using the supplied
|
||||||
|
* comment prefixes and statement separator, and build a {@code String} containing
|
||||||
|
* the lines.
|
||||||
|
* <p>Lines <em>beginning</em> with one of the comment prefixes 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 commentPrefixes the prefixes that identify comments in the SQL script
|
||||||
|
* (typically "--")
|
||||||
|
* @param separator the statement separator in the SQL script (typically ";")
|
||||||
|
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
||||||
|
* @return a {@code String} containing the script lines
|
||||||
|
* @throws IOException in case of I/O errors
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] commentPrefixes,
|
||||||
|
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
|
||||||
|
|
||||||
|
String currentStatement = lineNumberReader.readLine();
|
||||||
|
StringBuilder scriptBuilder = new StringBuilder();
|
||||||
|
while (currentStatement != null) {
|
||||||
|
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
|
||||||
|
(commentPrefixes != null && !startsWithAny(currentStatement, commentPrefixes, 0))) {
|
||||||
|
if (scriptBuilder.length() > 0) {
|
||||||
|
scriptBuilder.append('\n');
|
||||||
|
}
|
||||||
|
scriptBuilder.append(currentStatement);
|
||||||
|
}
|
||||||
|
currentStatement = lineNumberReader.readLine();
|
||||||
|
}
|
||||||
|
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
|
||||||
|
return scriptBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the provided SQL script contains the specified delimiter.
|
||||||
|
* <p>This method is intended to be used to find the string delimiting each
|
||||||
|
* SQL statement — for example, a ';' character.
|
||||||
|
* <p>Any occurrence of the delimiter within the script will be ignored if it
|
||||||
|
* is within a <em>literal</em> block of text enclosed in single quotes
|
||||||
|
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
|
||||||
|
* ({@code \}), or if it is within a single-line comment or block comment.
|
||||||
|
* @param script the SQL script to search within
|
||||||
|
* @param delimiter the statement delimiter to search for
|
||||||
|
* @see #DEFAULT_COMMENT_PREFIXES
|
||||||
|
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
|
||||||
|
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
|
||||||
|
*/
|
||||||
|
public static boolean containsSqlScriptDelimiters(String script, String delimiter) {
|
||||||
|
return containsStatementSeparator(null, script, delimiter, DEFAULT_COMMENT_PREFIXES,
|
||||||
|
DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the provided SQL script contains the specified statement separator.
|
||||||
|
* <p>This method is intended to be used to find the string separating each
|
||||||
|
* SQL statement — for example, a ';' character.
|
||||||
|
* <p>Any occurrence of the separator within the script will be ignored if it
|
||||||
|
* is within a <em>literal</em> block of text enclosed in single quotes
|
||||||
|
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
|
||||||
|
* ({@code \}), or if it is within a single-line comment or block comment.
|
||||||
|
* @param resource the resource from which the script was read, or {@code null}
|
||||||
|
* if unknown
|
||||||
|
* @param script the SQL script to search within
|
||||||
|
* @param separator the statement separator to search for
|
||||||
|
* @param commentPrefixes the prefixes that identify single-line comments
|
||||||
|
* (typically {@code "--"})
|
||||||
|
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
|
||||||
|
* (typically {@code "/*"})
|
||||||
|
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
||||||
|
* (typically <code>"*/"</code>)
|
||||||
|
* @since 5.2.16
|
||||||
|
*/
|
||||||
|
private static boolean containsStatementSeparator(@Nullable EncodedResource resource, String script,
|
||||||
|
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
||||||
|
String blockCommentEndDelimiter) throws ScriptException {
|
||||||
|
|
||||||
|
boolean inSingleQuote = false;
|
||||||
|
boolean inDoubleQuote = false;
|
||||||
|
boolean inEscape = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < script.length(); i++) {
|
||||||
|
char c = script.charAt(i);
|
||||||
|
if (inEscape) {
|
||||||
|
inEscape = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// MySQL style escapes
|
||||||
|
if (c == '\\') {
|
||||||
|
inEscape = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!inDoubleQuote && (c == '\'')) {
|
||||||
|
inSingleQuote = !inSingleQuote;
|
||||||
|
}
|
||||||
|
else if (!inSingleQuote && (c == '"')) {
|
||||||
|
inDoubleQuote = !inDoubleQuote;
|
||||||
|
}
|
||||||
|
if (!inSingleQuote && !inDoubleQuote) {
|
||||||
|
if (script.startsWith(separator, i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (startsWithAny(script, commentPrefixes, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
||||||
|
// Skip over any block comments
|
||||||
|
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
||||||
|
if (indexOfCommentEnd > i) {
|
||||||
|
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ScriptParseException(
|
||||||
|
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split an SQL script into separate statements delimited by the provided
|
||||||
|
* 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 (typically a ';')
|
||||||
|
* @param statements the list that will contain the individual statements
|
||||||
|
* @throws ScriptException if an error occurred while splitting the SQL script
|
||||||
|
* @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
|
||||||
|
* 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 text separating each statement
|
||||||
|
* (typically a ';' or newline character)
|
||||||
|
* @param statements the list that will contain the individual statements
|
||||||
|
* @throws ScriptException if an error occurred while splitting the SQL script
|
||||||
|
* @see #splitSqlScript(String, char, List)
|
||||||
|
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split an SQL script into separate statements delimited by the provided
|
||||||
|
* separator string. Each individual statement will be added to the provided
|
||||||
|
* {@code List}.
|
||||||
|
* <p>Within the script, 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 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
|
||||||
|
* @param separator text separating each statement
|
||||||
|
* (typically a ';' or newline character)
|
||||||
|
* @param commentPrefix the prefix that identifies SQL line comments
|
||||||
|
* (typically "--")
|
||||||
|
* @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
|
||||||
|
* @throws ScriptException if an error occurred while splitting the SQL script
|
||||||
|
*/
|
||||||
|
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
|
||||||
|
String separator, String commentPrefix, String blockCommentStartDelimiter,
|
||||||
|
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
|
||||||
|
|
||||||
|
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
|
||||||
|
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
|
||||||
|
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split an SQL script into separate statements delimited by the provided
|
||||||
|
* separator string. Each individual statement will be added to the provided
|
||||||
|
* {@code List}.
|
||||||
|
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
|
||||||
|
* any text beginning with one of the comment prefixes and extending to the
|
||||||
|
* end of the 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
|
||||||
|
* @param separator text separating each statement
|
||||||
|
* (typically a ';' or newline character)
|
||||||
|
* @param commentPrefixes the prefixes that identify SQL line comments
|
||||||
|
* (typically "--")
|
||||||
|
* @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
|
||||||
|
* @throws ScriptException if an error occurred while splitting the SQL script
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
|
||||||
|
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
||||||
|
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
|
||||||
|
|
||||||
|
Assert.hasText(script, "'script' must not be null or empty");
|
||||||
|
Assert.notNull(separator, "'separator' must not be null");
|
||||||
|
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
|
||||||
|
for (String commentPrefix : commentPrefixes) {
|
||||||
|
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
|
||||||
|
}
|
||||||
|
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
|
||||||
|
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean inSingleQuote = false;
|
||||||
|
boolean inDoubleQuote = false;
|
||||||
|
boolean inEscape = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < script.length(); i++) {
|
||||||
|
char c = script.charAt(i);
|
||||||
|
if (inEscape) {
|
||||||
|
inEscape = false;
|
||||||
|
sb.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// MySQL style escapes
|
||||||
|
if (c == '\\') {
|
||||||
|
inEscape = true;
|
||||||
|
sb.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!inDoubleQuote && (c == '\'')) {
|
||||||
|
inSingleQuote = !inSingleQuote;
|
||||||
|
}
|
||||||
|
else if (!inSingleQuote && (c == '"')) {
|
||||||
|
inDoubleQuote = !inDoubleQuote;
|
||||||
|
}
|
||||||
|
if (!inSingleQuote && !inDoubleQuote) {
|
||||||
|
if (script.startsWith(separator, i)) {
|
||||||
|
// We've reached the end of the current statement
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
statements.add(sb.toString());
|
||||||
|
sb = new StringBuilder();
|
||||||
|
}
|
||||||
|
i += separator.length() - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (startsWithAny(script, commentPrefixes, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
||||||
|
// Skip over any block comments
|
||||||
|
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
||||||
|
if (indexOfCommentEnd > i) {
|
||||||
|
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ScriptParseException(
|
||||||
|
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == ' ' || c == '\r' || 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
if (script.startsWith(prefix, offset)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic utility methods for working with SQL scripts.
|
* Generic utility methods for working with SQL scripts in conjunction with R2DBC.
|
||||||
*
|
*
|
||||||
* <p>Mainly for internal use within the framework.
|
* <p>Mainly for internal use within the framework.
|
||||||
*
|
*
|
||||||
|
|
@ -59,6 +59,7 @@ import org.springframework.util.StringUtils;
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
* @since 5.3
|
* @since 5.3
|
||||||
|
* @see org.springframework.jdbc.datasource.init.ScriptUtils
|
||||||
*/
|
*/
|
||||||
public abstract class ScriptUtils {
|
public abstract class ScriptUtils {
|
||||||
|
|
||||||
|
|
@ -104,280 +105,6 @@ public abstract class ScriptUtils {
|
||||||
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
|
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split an SQL script into separate statements delimited by the provided
|
|
||||||
* separator string and return a {@code List} containing each individual
|
|
||||||
* statement.
|
|
||||||
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
|
|
||||||
* any text beginning with one of the comment prefixes and extending to the
|
|
||||||
* end of the 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
|
|
||||||
* @param separator text separating each statement
|
|
||||||
* (typically a ';' or newline character)
|
|
||||||
* @param commentPrefixes the prefixes that identify SQL line comments
|
|
||||||
* (typically "--")
|
|
||||||
* @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
|
|
||||||
* @return a list of statements
|
|
||||||
* @throws ScriptException if an error occurred while splitting the SQL script
|
|
||||||
*/
|
|
||||||
static List<String> splitSqlScript(EncodedResource resource, String script,
|
|
||||||
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
|
||||||
String blockCommentEndDelimiter) throws ScriptException {
|
|
||||||
|
|
||||||
Assert.hasText(script, "'script' must not be null or empty");
|
|
||||||
Assert.notNull(separator, "'separator' must not be null");
|
|
||||||
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
|
|
||||||
for (String commentPrefix : commentPrefixes) {
|
|
||||||
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
|
|
||||||
}
|
|
||||||
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
|
|
||||||
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
|
|
||||||
|
|
||||||
List<String> statements = new ArrayList<>();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
boolean inSingleQuote = false;
|
|
||||||
boolean inDoubleQuote = false;
|
|
||||||
boolean inEscape = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < script.length(); i++) {
|
|
||||||
char c = script.charAt(i);
|
|
||||||
if (inEscape) {
|
|
||||||
inEscape = false;
|
|
||||||
sb.append(c);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// MySQL style escapes
|
|
||||||
if (c == '\\') {
|
|
||||||
inEscape = true;
|
|
||||||
sb.append(c);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!inDoubleQuote && (c == '\'')) {
|
|
||||||
inSingleQuote = !inSingleQuote;
|
|
||||||
}
|
|
||||||
else if (!inSingleQuote && (c == '"')) {
|
|
||||||
inDoubleQuote = !inDoubleQuote;
|
|
||||||
}
|
|
||||||
if (!inSingleQuote && !inDoubleQuote) {
|
|
||||||
if (script.startsWith(separator, i)) {
|
|
||||||
// We've reached the end of the current statement
|
|
||||||
if (sb.length() > 0) {
|
|
||||||
statements.add(sb.toString());
|
|
||||||
sb = new StringBuilder();
|
|
||||||
}
|
|
||||||
i += separator.length() - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (startsWithAny(script, commentPrefixes, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
|
||||||
// Skip over any block comments
|
|
||||||
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
|
||||||
if (indexOfCommentEnd > i) {
|
|
||||||
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new ScriptParseException(
|
|
||||||
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (c == ' ' || c == '\r' || 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
return statements;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a script from the provided resource, using the supplied statement
|
|
||||||
* separator, and build a {@code String} containing the lines.
|
|
||||||
* @param resource the {@code EncodedResource} containing the script to be
|
|
||||||
* processed
|
|
||||||
* @param dataBufferFactory the factory to create data buffers with
|
|
||||||
* @param separator the statement separator in the SQL script (typically ";")
|
|
||||||
* @return a {@link Mono} of {@link String} containing the script lines that
|
|
||||||
* completes once the resource has been loaded
|
|
||||||
*/
|
|
||||||
static Mono<String> readScript(EncodedResource resource, DataBufferFactory dataBufferFactory,
|
|
||||||
@Nullable String separator) {
|
|
||||||
|
|
||||||
return DataBufferUtils.join(DataBufferUtils.read(resource.getResource(), dataBufferFactory, 8192))
|
|
||||||
.handle((it, sink) -> {
|
|
||||||
try (InputStream is = it.asInputStream()) {
|
|
||||||
InputStreamReader in = (resource.getCharset() != null ?
|
|
||||||
new InputStreamReader(is, resource.getCharset()) : new InputStreamReader(is));
|
|
||||||
LineNumberReader lnr = new LineNumberReader(in);
|
|
||||||
String script = readScript(lnr, separator);
|
|
||||||
sink.next(script);
|
|
||||||
sink.complete();
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
sink.error(ex);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
DataBufferUtils.release(it);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a script from the provided {@code LineNumberReader} and build a
|
|
||||||
* {@code String} containing the lines.
|
|
||||||
* @param lineNumberReader the {@code LineNumberReader} containing the script
|
|
||||||
* to be processed
|
|
||||||
* @param separator the statement separator in the SQL script (typically ";")
|
|
||||||
* @return a {@code String} containing the script lines
|
|
||||||
* @throws IOException in case of I/O errors
|
|
||||||
*/
|
|
||||||
private static String readScript(LineNumberReader lineNumberReader, @Nullable String separator) throws IOException {
|
|
||||||
StringBuilder scriptBuilder = new StringBuilder();
|
|
||||||
String currentLine = lineNumberReader.readLine();
|
|
||||||
while (currentLine != null) {
|
|
||||||
if (scriptBuilder.length() > 0) {
|
|
||||||
scriptBuilder.append('\n');
|
|
||||||
}
|
|
||||||
scriptBuilder.append(currentLine);
|
|
||||||
currentLine = lineNumberReader.readLine();
|
|
||||||
}
|
|
||||||
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
|
|
||||||
return scriptBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable 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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
|
|
||||||
for (String prefix : prefixes) {
|
|
||||||
if (script.startsWith(prefix, offset)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the provided SQL script contains the specified statement separator.
|
|
||||||
* <p>This method is intended to be used to find the string separating each
|
|
||||||
* SQL statement — for example, a ';' character.
|
|
||||||
* <p>Any occurrence of the separator within the script will be ignored if it
|
|
||||||
* is within a <em>literal</em> block of text enclosed in single quotes
|
|
||||||
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
|
|
||||||
* ({@code \}), or if it is within a single-line comment or block comment.
|
|
||||||
* @param resource the resource from which the script was read, or {@code null}
|
|
||||||
* if unknown
|
|
||||||
* @param script the SQL script to search within
|
|
||||||
* @param separator the statement separator to search for
|
|
||||||
* @param commentPrefixes the prefixes that identify single-line comments
|
|
||||||
* (typically {@code "--"})
|
|
||||||
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
|
|
||||||
* (typically {@code "/*"})
|
|
||||||
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
|
||||||
* (typically <code>"*/"</code>)
|
|
||||||
* @since 5.3.8
|
|
||||||
*/
|
|
||||||
static boolean containsStatementSeparator(EncodedResource resource, String script,
|
|
||||||
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
|
||||||
String blockCommentEndDelimiter) throws ScriptException {
|
|
||||||
|
|
||||||
boolean inSingleQuote = false;
|
|
||||||
boolean inDoubleQuote = false;
|
|
||||||
boolean inEscape = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < script.length(); i++) {
|
|
||||||
char c = script.charAt(i);
|
|
||||||
if (inEscape) {
|
|
||||||
inEscape = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// MySQL style escapes
|
|
||||||
if (c == '\\') {
|
|
||||||
inEscape = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!inDoubleQuote && (c == '\'')) {
|
|
||||||
inSingleQuote = !inSingleQuote;
|
|
||||||
}
|
|
||||||
else if (!inSingleQuote && (c == '"')) {
|
|
||||||
inDoubleQuote = !inDoubleQuote;
|
|
||||||
}
|
|
||||||
if (!inSingleQuote && !inDoubleQuote) {
|
|
||||||
if (script.startsWith(separator, i)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (startsWithAny(script, commentPrefixes, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
|
||||||
// Skip over any block comments
|
|
||||||
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
|
||||||
if (indexOfCommentEnd > i) {
|
|
||||||
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new ScriptParseException(
|
|
||||||
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the given SQL script using default settings for statement
|
* Execute the given SQL script using default settings for statement
|
||||||
* separators, comment delimiters, and exception handling flags.
|
* separators, comment delimiters, and exception handling flags.
|
||||||
|
|
@ -546,6 +273,280 @@ public abstract class ScriptUtils {
|
||||||
.then();
|
.then();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided resource, using the supplied statement
|
||||||
|
* separator, and build a {@code String} containing the lines.
|
||||||
|
* @param resource the {@code EncodedResource} containing the script to be
|
||||||
|
* processed
|
||||||
|
* @param dataBufferFactory the factory to create data buffers with
|
||||||
|
* @param separator the statement separator in the SQL script (typically ";")
|
||||||
|
* @return a {@link Mono} of {@link String} containing the script lines that
|
||||||
|
* completes once the resource has been loaded
|
||||||
|
*/
|
||||||
|
static Mono<String> readScript(EncodedResource resource, DataBufferFactory dataBufferFactory,
|
||||||
|
@Nullable String separator) {
|
||||||
|
|
||||||
|
return DataBufferUtils.join(DataBufferUtils.read(resource.getResource(), dataBufferFactory, 8192))
|
||||||
|
.handle((it, sink) -> {
|
||||||
|
try (InputStream is = it.asInputStream()) {
|
||||||
|
InputStreamReader in = (resource.getCharset() != null ?
|
||||||
|
new InputStreamReader(is, resource.getCharset()) : new InputStreamReader(is));
|
||||||
|
LineNumberReader lnr = new LineNumberReader(in);
|
||||||
|
String script = readScript(lnr, separator);
|
||||||
|
sink.next(script);
|
||||||
|
sink.complete();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
sink.error(ex);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
DataBufferUtils.release(it);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided {@code LineNumberReader} and build a
|
||||||
|
* {@code String} containing the lines.
|
||||||
|
* @param lineNumberReader the {@code LineNumberReader} containing the script
|
||||||
|
* to be processed
|
||||||
|
* @param separator the statement separator in the SQL script (typically ";")
|
||||||
|
* @return a {@code String} containing the script lines
|
||||||
|
* @throws IOException in case of I/O errors
|
||||||
|
*/
|
||||||
|
private static String readScript(LineNumberReader lineNumberReader, @Nullable String separator) throws IOException {
|
||||||
|
StringBuilder scriptBuilder = new StringBuilder();
|
||||||
|
String currentLine = lineNumberReader.readLine();
|
||||||
|
while (currentLine != null) {
|
||||||
|
if (scriptBuilder.length() > 0) {
|
||||||
|
scriptBuilder.append('\n');
|
||||||
|
}
|
||||||
|
scriptBuilder.append(currentLine);
|
||||||
|
currentLine = lineNumberReader.readLine();
|
||||||
|
}
|
||||||
|
appendSeparatorToScriptIfNecessary(scriptBuilder, separator);
|
||||||
|
return scriptBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, @Nullable 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the provided SQL script contains the specified statement separator.
|
||||||
|
* <p>This method is intended to be used to find the string separating each
|
||||||
|
* SQL statement — for example, a ';' character.
|
||||||
|
* <p>Any occurrence of the separator within the script will be ignored if it
|
||||||
|
* is within a <em>literal</em> block of text enclosed in single quotes
|
||||||
|
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
|
||||||
|
* ({@code \}), or if it is within a single-line comment or block comment.
|
||||||
|
* @param resource the resource from which the script was read, or {@code null}
|
||||||
|
* if unknown
|
||||||
|
* @param script the SQL script to search within
|
||||||
|
* @param separator the statement separator to search for
|
||||||
|
* @param commentPrefixes the prefixes that identify single-line comments
|
||||||
|
* (typically {@code "--"})
|
||||||
|
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
|
||||||
|
* (typically {@code "/*"})
|
||||||
|
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
|
||||||
|
* (typically <code>"*/"</code>)
|
||||||
|
* @since 5.3.8
|
||||||
|
*/
|
||||||
|
static boolean containsStatementSeparator(EncodedResource resource, String script,
|
||||||
|
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
||||||
|
String blockCommentEndDelimiter) throws ScriptException {
|
||||||
|
|
||||||
|
boolean inSingleQuote = false;
|
||||||
|
boolean inDoubleQuote = false;
|
||||||
|
boolean inEscape = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < script.length(); i++) {
|
||||||
|
char c = script.charAt(i);
|
||||||
|
if (inEscape) {
|
||||||
|
inEscape = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// MySQL style escapes
|
||||||
|
if (c == '\\') {
|
||||||
|
inEscape = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!inDoubleQuote && (c == '\'')) {
|
||||||
|
inSingleQuote = !inSingleQuote;
|
||||||
|
}
|
||||||
|
else if (!inSingleQuote && (c == '"')) {
|
||||||
|
inDoubleQuote = !inDoubleQuote;
|
||||||
|
}
|
||||||
|
if (!inSingleQuote && !inDoubleQuote) {
|
||||||
|
if (script.startsWith(separator, i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (startsWithAny(script, commentPrefixes, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
||||||
|
// Skip over any block comments
|
||||||
|
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
||||||
|
if (indexOfCommentEnd > i) {
|
||||||
|
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ScriptParseException(
|
||||||
|
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split an SQL script into separate statements delimited by the provided
|
||||||
|
* separator string and return a {@code List} containing each individual
|
||||||
|
* statement.
|
||||||
|
* <p>Within the script, the provided {@code commentPrefixes} will be honored:
|
||||||
|
* any text beginning with one of the comment prefixes and extending to the
|
||||||
|
* end of the 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
|
||||||
|
* @param separator text separating each statement
|
||||||
|
* (typically a ';' or newline character)
|
||||||
|
* @param commentPrefixes the prefixes that identify SQL line comments
|
||||||
|
* (typically "--")
|
||||||
|
* @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
|
||||||
|
* @return a list of statements
|
||||||
|
* @throws ScriptException if an error occurred while splitting the SQL script
|
||||||
|
*/
|
||||||
|
static List<String> splitSqlScript(EncodedResource resource, String script,
|
||||||
|
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
|
||||||
|
String blockCommentEndDelimiter) throws ScriptException {
|
||||||
|
|
||||||
|
Assert.hasText(script, "'script' must not be null or empty");
|
||||||
|
Assert.notNull(separator, "'separator' must not be null");
|
||||||
|
Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty");
|
||||||
|
for (String commentPrefix : commentPrefixes) {
|
||||||
|
Assert.hasText(commentPrefix, "'commentPrefixes' must not contain null or empty elements");
|
||||||
|
}
|
||||||
|
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
|
||||||
|
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
|
||||||
|
|
||||||
|
List<String> statements = new ArrayList<>();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean inSingleQuote = false;
|
||||||
|
boolean inDoubleQuote = false;
|
||||||
|
boolean inEscape = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < script.length(); i++) {
|
||||||
|
char c = script.charAt(i);
|
||||||
|
if (inEscape) {
|
||||||
|
inEscape = false;
|
||||||
|
sb.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// MySQL style escapes
|
||||||
|
if (c == '\\') {
|
||||||
|
inEscape = true;
|
||||||
|
sb.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!inDoubleQuote && (c == '\'')) {
|
||||||
|
inSingleQuote = !inSingleQuote;
|
||||||
|
}
|
||||||
|
else if (!inSingleQuote && (c == '"')) {
|
||||||
|
inDoubleQuote = !inDoubleQuote;
|
||||||
|
}
|
||||||
|
if (!inSingleQuote && !inDoubleQuote) {
|
||||||
|
if (script.startsWith(separator, i)) {
|
||||||
|
// We've reached the end of the current statement
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
statements.add(sb.toString());
|
||||||
|
sb = new StringBuilder();
|
||||||
|
}
|
||||||
|
i += separator.length() - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (startsWithAny(script, commentPrefixes, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (script.startsWith(blockCommentStartDelimiter, i)) {
|
||||||
|
// Skip over any block comments
|
||||||
|
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
||||||
|
if (indexOfCommentEnd > i) {
|
||||||
|
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ScriptParseException(
|
||||||
|
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == ' ' || c == '\r' || 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean startsWithAny(String script, String[] prefixes, int offset) {
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
if (script.startsWith(prefix, offset)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static Publisher<? extends Void> runStatement(String statement, Connection connection,
|
private static Publisher<? extends Void> runStatement(String statement, Connection connection,
|
||||||
EncodedResource resource, boolean continueOnError, boolean ignoreFailedDrops, AtomicInteger statementNumber) {
|
EncodedResource resource, boolean continueOnError, boolean ignoreFailedDrops, AtomicInteger statementNumber) {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue