Skip to content

Commit 2225696

Browse files
committed
Ignore delimiter enclosed in double quotes in ScriptUtils
Prior to this commit, the containsSqlScriptDelimiters() method in ScriptUtils ignored delimiters enclosed in single quotes but not those enclosed within double quotes, which contradicts the algorithm in splitSqlScript() and therefore constitutes a bug. This commit fixes this bug in the ScriptUtils implementation in spring-jdbc. Closes gh-26935
1 parent 7c74459 commit 2225696

File tree

2 files changed

+43
-22
lines changed

2 files changed

+43
-22
lines changed

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

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -414,12 +414,18 @@ private static boolean startsWithAny(String script, String[] prefixes, int offse
414414
}
415415

416416
/**
417-
* Does the provided SQL script contain the specified delimiter?
418-
* @param script the SQL script
419-
* @param delim the string delimiting each statement - typically a ';' character
417+
* Determine if the provided SQL script contains the specified delimiter.
418+
* <p>This method is intended to be used to find the string delimiting each
419+
* SQL statement &mdash; for example, a ';' character.
420+
* <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 \}).
423+
* @param script the SQL script to search within
424+
* @param delimiter the delimiter to search for
420425
*/
421-
public static boolean containsSqlScriptDelimiters(String script, String delim) {
422-
boolean inLiteral = false;
426+
public static boolean containsSqlScriptDelimiters(String script, String delimiter) {
427+
boolean inSingleQuote = false;
428+
boolean inDoubleQuote = false;
423429
boolean inEscape = false;
424430

425431
for (int i = 0; i < script.length(); i++) {
@@ -433,11 +439,16 @@ public static boolean containsSqlScriptDelimiters(String script, String delim) {
433439
inEscape = true;
434440
continue;
435441
}
436-
if (c == '\'') {
437-
inLiteral = !inLiteral;
442+
if (!inDoubleQuote && (c == '\'')) {
443+
inSingleQuote = !inSingleQuote;
438444
}
439-
if (!inLiteral && script.startsWith(delim, i)) {
440-
return true;
445+
else if (!inSingleQuote && (c == '"')) {
446+
inDoubleQuote = !inDoubleQuote;
447+
}
448+
if (!inSingleQuote && !inDoubleQuote) {
449+
if (script.startsWith(delimiter, i)) {
450+
return true;
451+
}
441452
}
442453
}
443454

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@
2020
import java.util.List;
2121

2222
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.CsvSource;
2325

2426
import org.springframework.core.io.ClassPathResource;
2527
import org.springframework.core.io.support.EncodedResource;
@@ -165,17 +167,25 @@ public void readAndSplitScriptContainingMultiLineNestedComments() throws Excepti
165167
assertThat(statements).containsExactly(statement1, statement2);
166168
}
167169

168-
@Test
169-
public void containsDelimiters() {
170-
assertThat(containsSqlScriptDelimiters("select 1\n select ';'", ";")).isFalse();
171-
assertThat(containsSqlScriptDelimiters("select 1; select 2", ";")).isTrue();
172-
assertThat(containsSqlScriptDelimiters("select 1; select '\\n\n';", "\n")).isFalse();
173-
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n")).isTrue();
174-
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n\n")).isFalse();
175-
assertThat(containsSqlScriptDelimiters("select 1\n\n select 2", "\n\n")).isTrue();
176-
// MySQL style escapes '\\'
177-
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('a\\\\', 'b;')", ";")).isFalse();
178-
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('Charles', 'd\\'Artagnan'); select 1;", ";")).isTrue();
170+
@ParameterizedTest
171+
@CsvSource(delimiter = '#', value = {
172+
// semicolon
173+
"'select 1\n select '';''' # ; # false",
174+
"'select 1\n select \";\"' # ; # false",
175+
"'select 1; select 2' # ; # true",
176+
// newline
177+
"'select 1; select ''\n''' # '\n' # false",
178+
"'select 1; select \"\n\"' # '\n' # false",
179+
"'select 1\n select 2' # '\n' # true",
180+
// double newline
181+
"'select 1\n select 2' # '\n\n' # false",
182+
"'select 1\n\n select 2' # '\n\n' # true",
183+
// semicolon with MySQL style escapes '\\'
184+
"'insert into users(first, last)\nvalues(''a\\\\'', ''b;'')' # ; # false",
185+
"'insert into users(first, last)\nvalues(''Charles'', ''d\\''Artagnan''); select 1' # ; # true"
186+
})
187+
public void containsDelimiter(String script, String delimiter, boolean expected) {
188+
assertThat(containsSqlScriptDelimiters(script, delimiter)).isEqualTo(expected);
179189
}
180190

181191
private String readScript(String path) throws Exception {

0 commit comments

Comments
 (0)