Skip to content

Commit 629bcb6

Browse files
ndebeisssbrannen
authored andcommitted
Support single quotes nested in double quotes in SQL scripts
Some databases such as Oracle permit double quoted column aliases that contain case-sensitive characters, single quotes, and other special characters; however, prior to this commit, SqlScripts interpreted a single quote nested within double quotes as the start of a string literal resulting in improper parsing. This commit addresses this issue by ensuring that double quoted strings such as column aliases are properly parsed even when containing single quotes. Issue: SPR-13218
1 parent de6bbe7 commit 629bcb6

File tree

4 files changed

+51
-9
lines changed

4 files changed

+51
-9
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* @author Chris Beams
4646
* @author Oliver Gierke
4747
* @author Chris Baldwin
48+
* @author Nicolas Debeissat
4849
* @since 4.0.3
4950
*/
5051
public abstract class ScriptUtils {
@@ -173,7 +174,8 @@ public static void splitSqlScript(EncodedResource resource, String script, Strin
173174
Assert.hasText(blockCommentEndDelimiter, "blockCommentEndDelimiter must not be null or empty");
174175

175176
StringBuilder sb = new StringBuilder();
176-
boolean inLiteral = false;
177+
boolean inSingleQuote = false;
178+
boolean inDoubleQuote = false;
177179
boolean inEscape = false;
178180
char[] content = script.toCharArray();
179181
for (int i = 0; i < script.length(); i++) {
@@ -189,10 +191,13 @@ public static void splitSqlScript(EncodedResource resource, String script, Strin
189191
sb.append(c);
190192
continue;
191193
}
192-
if (c == '\'') {
193-
inLiteral = !inLiteral;
194+
if (!inDoubleQuote && (c == '\'')) {
195+
inSingleQuote = !inSingleQuote;
196+
}
197+
else if (!inSingleQuote && (c == '"')) {
198+
inDoubleQuote = !inDoubleQuote;
194199
}
195-
if (!inLiteral) {
200+
if (!inSingleQuote && !inDoubleQuote) {
196201
if (script.startsWith(separator, i)) {
197202
// we've reached the end of the current statement
198203
if (sb.length() > 0) {

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -18,6 +18,7 @@
1818

1919
import java.sql.SQLException;
2020

21+
import org.junit.Before;
2122
import org.junit.Test;
2223

2324
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@@ -28,20 +29,32 @@
2829
* Integration tests for {@link ScriptUtils}.
2930
*
3031
* @author Sam Brannen
31-
* @see ScriptUtilsUnitTests
3232
* @since 4.0.3
33+
* @see ScriptUtilsUnitTests
3334
*/
3435
public class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests {
3536

3637
protected EmbeddedDatabaseType getEmbeddedDatabaseType() {
3738
return EmbeddedDatabaseType.HSQL;
3839
}
3940

41+
@Before
42+
public void setUpSchema() throws SQLException {
43+
executeSqlScript(db.getConnection(), usersSchema());
44+
}
45+
4046
@Test
4147
public void executeSqlScriptContainingMuliLineComments() throws SQLException {
42-
executeSqlScript(db.getConnection(), usersSchema());
4348
executeSqlScript(db.getConnection(), resource("test-data-with-multi-line-comments.sql"));
49+
assertUsersDatabaseCreated("Hoeller", "Brannen");
50+
}
4451

52+
/**
53+
* @since 4.2
54+
*/
55+
@Test
56+
public void executeSqlScriptContainingSingleQuotesNestedInsideDoubleQuotes() throws SQLException {
57+
executeSqlScript(db.getConnection(), resource("users-data-with-single-quotes-nested-in-double-quotes.sql"));
4558
assertUsersDatabaseCreated("Hoeller", "Brannen");
4659
}
4760

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -34,8 +34,9 @@
3434
* @author Sam Brannen
3535
* @author Phillip Webb
3636
* @author Chris Baldwin
37-
* @see ScriptUtilsIntegrationTests
37+
* @author Nicolas Debeissat
3838
* @since 4.0.3
39+
* @see ScriptUtilsIntegrationTests
3940
*/
4041
public class ScriptUtilsUnitTests {
4142

@@ -85,6 +86,22 @@ public void splitSqlScriptDelimitedWithNewLineButDefaultDelimiterSpecified() {
8586
statements.get(0));
8687
}
8788

89+
/**
90+
* See <a href="https://jira.spring.io/browse/SPR-13218">SPR-13218</a>
91+
*/
92+
@Test
93+
public void splitScriptWithSingleQuotesNestedInsideDoubleQuotes() throws Exception {
94+
String statement1 = "select '1' as \"Dogbert's owner's\" from dual";
95+
String statement2 = "select '2' as \"Dilbert's\" from dual";
96+
char delim = ';';
97+
String script = statement1 + delim + statement2 + delim;
98+
List<String> statements = new ArrayList<String>();
99+
splitSqlScript(script, ';', statements);
100+
assertEquals("wrong number of statements", 2, statements.size());
101+
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
102+
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
103+
}
104+
88105
/**
89106
* See <a href="https://jira.spring.io/browse/SPR-11560">SPR-11560</a>
90107
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
2+
3+
-- The following is not actually used; we just want to ensure that it does not
4+
-- result in a parsing exception due to the nested single quote.
5+
SELECT last_name AS "Juergen's Last Name" FROM users WHERE last_name='Hoeller';
6+
7+
INSERT INTO users(first_name, last_name) values('Sam', 'Brannen');

0 commit comments

Comments
 (0)