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 271eff0210..6397c45d48 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 @@ -418,12 +418,44 @@ public abstract class ScriptUtils { *
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 enclosed within single quotes ({@code '}) or double quotes ({@code "}) - * or if it is escaped with a backslash ({@code \}). + * 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 delimiter to search for + * @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;
@@ -446,9 +478,33 @@ public abstract class ScriptUtils {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
- if (script.startsWith(delimiter, i)) {
+ 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);
+ }
+ }
}
}
@@ -595,7 +651,9 @@ public abstract class ScriptUtils {
if (separator == null) {
separator = DEFAULT_STATEMENT_SEPARATOR;
}
- if (!EOF_STATEMENT_SEPARATOR.equals(separator) && !containsSqlScriptDelimiters(script, separator)) {
+ if (!EOF_STATEMENT_SEPARATOR.equals(separator) &&
+ !containsStatementSeparator(resource, script, separator, commentPrefixes,
+ blockCommentStartDelimiter, blockCommentEndDelimiter)) {
separator = FALLBACK_STATEMENT_SEPARATOR;
}
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java
index 25c2e7f13a..8b07ce617c 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java
@@ -205,9 +205,25 @@ public class ScriptUtilsUnitTests {
"'select 1\n\n select 2' # '\n\n' # true",
// semicolon with MySQL style escapes '\\'
"'insert into users(first, last)\nvalues(''a\\\\'', ''b;'')' # ; # false",
- "'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true"
+ "'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true",
+ // semicolon inside comments
+ "'-- a;b;c\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* a;b;c */\ninsert into colors(color_num) values(42);' # ; # true",
+ "'-- a;b;c\ninsert into colors(color_num) values(42)' # ; # false",
+ "'/* a;b;c */\ninsert into colors(color_num) values(42)' # ; # false",
+ // single quotes inside comments
+ "'-- What\\''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
+ "'-- What''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* What\\''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* What''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
+ // double quotes inside comments
+ "'-- double \" quotes\ninsert into colors(color_num) values(42);' # ; # true",
+ "'-- double \\\" quotes\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* double \" quotes */\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* double \\\" quotes */\ninsert into colors(color_num) values(42);' # ; # true"
})
- public void containsDelimiter(String script, String delimiter, boolean expected) {
+ public void containsStatementSeparator(String script, String delimiter, boolean expected) {
+ // Indirectly tests ScriptUtils.containsStatementSeparator(EncodedResource, String, String, String[], String, String).
assertThat(containsSqlScriptDelimiters(script, delimiter)).isEqualTo(expected);
}
diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-nested-comments.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-nested-comments.sql
index 7faa91c250..5df377ccad 100644
--- a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-nested-comments.sql
+++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-nested-comments.sql
@@ -5,16 +5,19 @@
* x, y, z...
*/
+-- This is a single line comment containing single (') and double quotes (").
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
-- This is also a comment.
/*-------------------------------------------
--- A fancy multi-line comments that puts
+-- A fancy multi-line comment that puts
-- single line comments inside of a multi-line
-- comment block.
Moreover, the block comment end delimiter
appears on a line that can potentially also
be a single-line comment if we weren't
already inside a multi-line comment run.
+
+And here's a line containing single and double quotes (").
-------------------------------------------*/
INSERT INTO
users(first_name, last_name) -- This is a single line comment containing the block-end-comment sequence here */ but it's still a single-line comment
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 6cd2b6a490..6dddcc1632 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
@@ -436,12 +436,44 @@ public abstract class ScriptUtils {
*
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 enclosed within single quotes ({@code '}) or double quotes ({@code "}) - * or if it is escaped with a backslash ({@code \}). + * 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 delimiter to search for + * @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;
@@ -464,9 +496,33 @@ public abstract class ScriptUtils {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
- if (script.startsWith(delimiter, i)) {
+ 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);
+ }
+ }
}
}
diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/init/ScriptUtilsUnitTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/init/ScriptUtilsUnitTests.java
index e98c5dafc0..334afe6e3a 100644
--- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/init/ScriptUtilsUnitTests.java
+++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/init/ScriptUtilsUnitTests.java
@@ -207,9 +207,25 @@ public class ScriptUtilsUnitTests {
"'select 1\n\n select 2' # '\n\n' # true",
// semicolon with MySQL style escapes '\\'
"'insert into users(first, last)\nvalues(''a\\\\'', ''b;'')' # ; # false",
- "'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true"
+ "'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true",
+ // semicolon inside comments
+ "'-- a;b;c\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* a;b;c */\ninsert into colors(color_num) values(42);' # ; # true",
+ "'-- a;b;c\ninsert into colors(color_num) values(42)' # ; # false",
+ "'/* a;b;c */\ninsert into colors(color_num) values(42)' # ; # false",
+ // single quotes inside comments
+ "'-- What\\''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
+ "'-- What''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* What\\''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* What''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
+ // double quotes inside comments
+ "'-- double \" quotes\ninsert into colors(color_num) values(42);' # ; # true",
+ "'-- double \\\" quotes\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* double \" quotes */\ninsert into colors(color_num) values(42);' # ; # true",
+ "'/* double \\\" quotes */\ninsert into colors(color_num) values(42);' # ; # true"
})
- public void containsDelimiter(String script, String delimiter, boolean expected) {
+ public void containsStatementSeparator(String script, String delimiter, boolean expected) {
+ // Indirectly tests ScriptUtils.containsStatementSeparator(EncodedResource, String, String, String[], String, String).
assertThat(containsSqlScriptDelimiters(script, delimiter)).isEqualTo(expected);
}
diff --git a/spring-r2dbc/src/test/resources/org/springframework/r2dbc/connection/init/test-data-with-multi-line-nested-comments.sql b/spring-r2dbc/src/test/resources/org/springframework/r2dbc/connection/init/test-data-with-multi-line-nested-comments.sql
index 7faa91c250..5df377ccad 100644
--- a/spring-r2dbc/src/test/resources/org/springframework/r2dbc/connection/init/test-data-with-multi-line-nested-comments.sql
+++ b/spring-r2dbc/src/test/resources/org/springframework/r2dbc/connection/init/test-data-with-multi-line-nested-comments.sql
@@ -5,16 +5,19 @@
* x, y, z...
*/
+-- This is a single line comment containing single (') and double quotes (").
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
-- This is also a comment.
/*-------------------------------------------
--- A fancy multi-line comments that puts
+-- A fancy multi-line comment that puts
-- single line comments inside of a multi-line
-- comment block.
Moreover, the block comment end delimiter
appears on a line that can potentially also
be a single-line comment if we weren't
already inside a multi-line comment run.
+
+And here's a line containing single and double quotes (").
-------------------------------------------*/
INSERT INTO
users(first_name, last_name) -- This is a single line comment containing the block-end-comment sequence here */ but it's still a single-line comment