From 4c642ccdcd2045bda2b3ce628768d63cb7e01486 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 18 May 2021 14:52:19 +0200 Subject: [PATCH] Logically reorganize methods in ScriptUtils See gh-26947 --- .../jdbc/datasource/init/ScriptUtils.java | 801 +++++++++--------- .../r2dbc/connection/init/ScriptUtils.java | 551 ++++++------ 2 files changed, 677 insertions(+), 675 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java index 789007521a0..c3e315d0b31 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java @@ -35,7 +35,7 @@ import org.springframework.util.Assert; 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. * *

Mainly for internal use within the framework. * @@ -50,6 +50,7 @@ import org.springframework.util.StringUtils; * @author Nicolas Debeissat * @author Phillip Webb * @since 4.0.3 + * @see org.springframework.r2dbc.connection.init.ScriptUtils */ public abstract class ScriptUtils { @@ -101,405 +102,6 @@ public abstract class ScriptUtils { 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}. - *

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 - * start and end 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 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}. - *

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 - * start and end 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 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}. - *

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 start block comment delimiter; - * never {@code null} or empty - * @param blockCommentEndDelimiter the end 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 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}. - *

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 start block comment delimiter; - * never {@code null} or empty - * @param blockCommentEndDelimiter the end 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 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. - *

Lines beginning 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 end 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. - *

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 ";") - * @param blockCommentEndDelimiter the end 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. - *

Lines beginning 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 end 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. - *

This method is intended to be used to find the string delimiting each - * SQL statement — for example, a ';' character. - *

Any occurrence of the delimiter within the script will be ignored if it - * is within a literal 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. - *

This method is intended to be used to find the string separating each - * SQL statement — for example, a ';' character. - *

Any occurrence of the separator within the script will be ignored if it - * is within a literal 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 start block comment delimiter - * (typically {@code "/*"}) - * @param blockCommentEndDelimiter the end block comment delimiter - * (typically "*/") - * @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 * 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. + *

Lines beginning 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 end 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. + *

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 ";") + * @param blockCommentEndDelimiter the end 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. + *

Lines beginning 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 end 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. + *

This method is intended to be used to find the string delimiting each + * SQL statement — for example, a ';' character. + *

Any occurrence of the delimiter within the script will be ignored if it + * is within a literal 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. + *

This method is intended to be used to find the string separating each + * SQL statement — for example, a ';' character. + *

Any occurrence of the separator within the script will be ignored if it + * is within a literal 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 start block comment delimiter + * (typically {@code "/*"}) + * @param blockCommentEndDelimiter the end block comment delimiter + * (typically "*/") + * @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}. + *

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 + * start and end 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 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}. + *

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 + * start and end 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 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}. + *

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 start block comment delimiter; + * never {@code null} or empty + * @param blockCommentEndDelimiter the end 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 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}. + *

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 start block comment delimiter; + * never {@code null} or empty + * @param blockCommentEndDelimiter the end 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 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; + } + } diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java index 304f6ec5580..82fdf38ee3e 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/init/ScriptUtils.java @@ -43,7 +43,7 @@ import org.springframework.util.Assert; 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. * *

Mainly for internal use within the framework. * @@ -59,6 +59,7 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Mark Paluch * @since 5.3 + * @see org.springframework.jdbc.datasource.init.ScriptUtils */ public abstract class ScriptUtils { @@ -104,280 +105,6 @@ public abstract class ScriptUtils { 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. - *

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 start block comment delimiter; - * never {@code null} or empty - * @param blockCommentEndDelimiter the end 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 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 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 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. - *

This method is intended to be used to find the string separating each - * SQL statement — for example, a ';' character. - *

Any occurrence of the separator within the script will be ignored if it - * is within a literal 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 start block comment delimiter - * (typically {@code "/*"}) - * @param blockCommentEndDelimiter the end block comment delimiter - * (typically "*/") - * @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 * separators, comment delimiters, and exception handling flags. @@ -546,6 +273,280 @@ public abstract class ScriptUtils { .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 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. + *

This method is intended to be used to find the string separating each + * SQL statement — for example, a ';' character. + *

Any occurrence of the separator within the script will be ignored if it + * is within a literal 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 start block comment delimiter + * (typically {@code "/*"}) + * @param blockCommentEndDelimiter the end block comment delimiter + * (typically "*/") + * @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. + *

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 start block comment delimiter; + * never {@code null} or empty + * @param blockCommentEndDelimiter the end 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 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 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 runStatement(String statement, Connection connection, EncodedResource resource, boolean continueOnError, boolean ignoreFailedDrops, AtomicInteger statementNumber) {