Skip to content

Commit e4d843e

Browse files
committed
Ignore comments when searching for statement delimiter in ScriptUtils
Prior to this commit, the implementation of ScriptUtils.containsSqlScriptDelimiters() did not ignore comments when searching for the statement delimiter within an SQL script. This resulted in subtle bugs if a comment contained a single single-quote or single double-quote, since the absence of the closing single-quote or double-quote led the algorithm to believe that it was still "within a text literal". Similar issues could arise if a comment contained the sought statement delimiter but the rest of the script did not contain the sought statement delimiter. In such cases, the algorithms in ScriptUtils could erroneously choose an incorrect statement delimiter -- for example, using the fallback statement delimiter instead of the delimiter specified by the user. This commit avoids such bugs by ignoring single-line comments and block comments when searching for the statement delimiter within an SQL script. Closes gh-26911
1 parent 2225696 commit e4d843e

File tree

3 files changed

+85
-8
lines changed

3 files changed

+85
-8
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -418,12 +418,44 @@ private static boolean startsWithAny(String script, String[] prefixes, int offse
418418
* <p>This method is intended to be used to find the string delimiting each
419419
* SQL statement &mdash; for example, a ';' character.
420420
* <p>Any occurrence of the delimiter within the script will be ignored if it
421-
* is enclosed within single quotes ({@code '}) or double quotes ({@code "})
422-
* or if it is escaped with a backslash ({@code \}).
421+
* is within a <em>literal</em> block of text enclosed in single quotes
422+
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
423+
* ({@code \}), or if it is within a single-line comment or block comment.
423424
* @param script the SQL script to search within
424-
* @param delimiter the delimiter to search for
425+
* @param delimiter the statement delimiter to search for
426+
* @see #DEFAULT_COMMENT_PREFIXES
427+
* @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER
428+
* @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER
425429
*/
426430
public static boolean containsSqlScriptDelimiters(String script, String delimiter) {
431+
return containsStatementSeparator(null, script, delimiter, DEFAULT_COMMENT_PREFIXES,
432+
DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
433+
}
434+
435+
/**
436+
* Determine if the provided SQL script contains the specified statement separator.
437+
* <p>This method is intended to be used to find the string separating each
438+
* SQL statement &mdash; for example, a ';' character.
439+
* <p>Any occurrence of the separator within the script will be ignored if it
440+
* is within a <em>literal</em> block of text enclosed in single quotes
441+
* ({@code '}) or double quotes ({@code "}), if it is escaped with a backslash
442+
* ({@code \}), or if it is within a single-line comment or block comment.
443+
* @param resource the resource from which the script was read, or {@code null}
444+
* if unknown
445+
* @param script the SQL script to search within
446+
* @param separator the statement separator to search for
447+
* @param commentPrefixes the prefixes that identify single-line comments
448+
* (typically {@code "--"})
449+
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
450+
* (typically {@code "/*"})
451+
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
452+
* (typically <code>"*&#47;"</code>)
453+
* @since 5.2.16
454+
*/
455+
private static boolean containsStatementSeparator(@Nullable EncodedResource resource, String script,
456+
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
457+
String blockCommentEndDelimiter) throws ScriptException {
458+
427459
boolean inSingleQuote = false;
428460
boolean inDoubleQuote = false;
429461
boolean inEscape = false;
@@ -446,9 +478,33 @@ else if (!inSingleQuote && (c == '"')) {
446478
inDoubleQuote = !inDoubleQuote;
447479
}
448480
if (!inSingleQuote && !inDoubleQuote) {
449-
if (script.startsWith(delimiter, i)) {
481+
if (script.startsWith(separator, i)) {
450482
return true;
451483
}
484+
else if (startsWithAny(script, commentPrefixes, i)) {
485+
// Skip over any content from the start of the comment to the EOL
486+
int indexOfNextNewline = script.indexOf('\n', i);
487+
if (indexOfNextNewline > i) {
488+
i = indexOfNextNewline;
489+
continue;
490+
}
491+
else {
492+
// If there's no EOL, we must be at the end of the script, so stop here.
493+
break;
494+
}
495+
}
496+
else if (script.startsWith(blockCommentStartDelimiter, i)) {
497+
// Skip over any block comments
498+
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
499+
if (indexOfCommentEnd > i) {
500+
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
501+
continue;
502+
}
503+
else {
504+
throw new ScriptParseException(
505+
"Missing block comment end delimiter: " + blockCommentEndDelimiter, resource);
506+
}
507+
}
452508
}
453509
}
454510

@@ -595,7 +651,9 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
595651
if (separator == null) {
596652
separator = DEFAULT_STATEMENT_SEPARATOR;
597653
}
598-
if (!EOF_STATEMENT_SEPARATOR.equals(separator) && !containsSqlScriptDelimiters(script, separator)) {
654+
if (!EOF_STATEMENT_SEPARATOR.equals(separator) &&
655+
!containsStatementSeparator(resource, script, separator, commentPrefixes,
656+
blockCommentStartDelimiter, blockCommentEndDelimiter)) {
599657
separator = FALLBACK_STATEMENT_SEPARATOR;
600658
}
601659

spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,25 @@ public void readAndSplitScriptContainingMultiLineNestedComments() throws Excepti
182182
"'select 1\n\n select 2' # '\n\n' # true",
183183
// semicolon with MySQL style escapes '\\'
184184
"'insert into users(first, last)\nvalues(''a\\\\'', ''b;'')' # ; # false",
185-
"'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true"
185+
"'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true",
186+
// semicolon inside comments
187+
"'-- a;b;c\ninsert into colors(color_num) values(42);' # ; # true",
188+
"'/* a;b;c */\ninsert into colors(color_num) values(42);' # ; # true",
189+
"'-- a;b;c\ninsert into colors(color_num) values(42)' # ; # false",
190+
"'/* a;b;c */\ninsert into colors(color_num) values(42)' # ; # false",
191+
// single quotes inside comments
192+
"'-- What\\''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
193+
"'-- What''s your favorite color?\ninsert into colors(color_num) values(42);' # ; # true",
194+
"'/* What\\''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
195+
"'/* What''s your favorite color? */\ninsert into colors(color_num) values(42);' # ; # true",
196+
// double quotes inside comments
197+
"'-- double \" quotes\ninsert into colors(color_num) values(42);' # ; # true",
198+
"'-- double \\\" quotes\ninsert into colors(color_num) values(42);' # ; # true",
199+
"'/* double \" quotes */\ninsert into colors(color_num) values(42);' # ; # true",
200+
"'/* double \\\" quotes */\ninsert into colors(color_num) values(42);' # ; # true"
186201
})
187-
public void containsDelimiter(String script, String delimiter, boolean expected) {
202+
public void containsStatementSeparator(String script, String delimiter, boolean expected) {
203+
// Indirectly tests ScriptUtils.containsStatementSeparator(EncodedResource, String, String, String[], String, String).
188204
assertThat(containsSqlScriptDelimiters(script, delimiter)).isEqualTo(expected);
189205
}
190206

spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-nested-comments.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
* x, y, z...
66
*/
77

8+
-- This is a single line comment containing single (') and double quotes (").
89
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
910
-- This is also a comment.
1011
/*-------------------------------------------
11-
-- A fancy multi-line comments that puts
12+
-- A fancy multi-line comment that puts
1213
-- single line comments inside of a multi-line
1314
-- comment block.
1415
Moreover, the block comment end delimiter
1516
appears on a line that can potentially also
1617
be a single-line comment if we weren't
1718
already inside a multi-line comment run.
19+
20+
And here's a line containing single and double quotes (").
1821
-------------------------------------------*/
1922
INSERT INTO
2023
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

0 commit comments

Comments
 (0)