Skip to content

Commit

Permalink
Ignore delimiter enclosed in double quotes in ScriptUtils
Browse files Browse the repository at this point in the history
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 spring-projectsgh-26935
  • Loading branch information
sbrannen committed May 12, 2021
1 parent 7c74459 commit 2225696
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -414,12 +414,18 @@ private static boolean startsWithAny(String script, String[] prefixes, int offse
}

/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim the string delimiting each statement - typically a ';' character
* 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 &mdash; for example, a ';' character.
* <p>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 \}).
* @param script the SQL script to search within
* @param delimiter the delimiter to search for
*/
public static boolean containsSqlScriptDelimiters(String script, String delim) {
boolean inLiteral = false;
public static boolean containsSqlScriptDelimiters(String script, String delimiter) {
boolean inSingleQuote = false;
boolean inDoubleQuote = false;
boolean inEscape = false;

for (int i = 0; i < script.length(); i++) {
Expand All @@ -433,11 +439,16 @@ public static boolean containsSqlScriptDelimiters(String script, String delim) {
inEscape = true;
continue;
}
if (c == '\'') {
inLiteral = !inLiteral;
if (!inDoubleQuote && (c == '\'')) {
inSingleQuote = !inSingleQuote;
}
if (!inLiteral && script.startsWith(delim, i)) {
return true;
else if (!inSingleQuote && (c == '"')) {
inDoubleQuote = !inDoubleQuote;
}
if (!inSingleQuote && !inDoubleQuote) {
if (script.startsWith(delimiter, i)) {
return true;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,8 @@
import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
Expand Down Expand Up @@ -165,17 +167,25 @@ public void readAndSplitScriptContainingMultiLineNestedComments() throws Excepti
assertThat(statements).containsExactly(statement1, statement2);
}

@Test
public void containsDelimiters() {
assertThat(containsSqlScriptDelimiters("select 1\n select ';'", ";")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1; select 2", ";")).isTrue();
assertThat(containsSqlScriptDelimiters("select 1; select '\\n\n';", "\n")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n")).isTrue();
assertThat(containsSqlScriptDelimiters("select 1\n select 2", "\n\n")).isFalse();
assertThat(containsSqlScriptDelimiters("select 1\n\n select 2", "\n\n")).isTrue();
// MySQL style escapes '\\'
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('a\\\\', 'b;')", ";")).isFalse();
assertThat(containsSqlScriptDelimiters("insert into users(first_name, last_name)\nvalues('Charles', 'd\\'Artagnan'); select 1;", ";")).isTrue();
@ParameterizedTest
@CsvSource(delimiter = '#', value = {
// semicolon
"'select 1\n select '';''' # ; # false",
"'select 1\n select \";\"' # ; # false",
"'select 1; select 2' # ; # true",
// newline
"'select 1; select ''\n''' # '\n' # false",
"'select 1; select \"\n\"' # '\n' # false",
"'select 1\n select 2' # '\n' # true",
// double newline
"'select 1\n select 2' # '\n\n' # false",
"'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"
})
public void containsDelimiter(String script, String delimiter, boolean expected) {
assertThat(containsSqlScriptDelimiters(script, delimiter)).isEqualTo(expected);
}

private String readScript(String path) throws Exception {
Expand Down

0 comments on commit 2225696

Please sign in to comment.