Skip to content

Commit d8dc4a2

Browse files
sbrannencbeams
authored andcommitted
Support comments in SQL scripts in JdbcTestUtils
Prior to this commit, utility methods in JdbcTestUtils interpreted SQL comments as separate statements, resulting in an exception when such a script is executed. This commit addresses this issue by introducing a readScript(lineNumberReader, String) method that accepts a comment prefix. Comment lines are therefore no longer returned in the parsed script. Furthermore, the existing readScript(lineNumberReader) method now delegates to this new readScript() method, supplying "--" as the default comment prefix. Issue: SPR-9593 Backport-Commit: 4aaf014
1 parent 00ed17d commit d8dc4a2

File tree

5 files changed

+147
-40
lines changed

5 files changed

+147
-40
lines changed

org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -19,17 +19,18 @@
1919
import org.springframework.core.io.support.EncodedResource;
2020

2121
/**
22-
* Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts could
23-
* not be read during population.
22+
* Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts cannot
23+
* be read during population.
2424
*
2525
* @author Keith Donald
2626
* @since 3.0
2727
*/
28+
@SuppressWarnings("serial")
2829
public class CannotReadScriptException extends RuntimeException {
2930

3031
/**
31-
* Constructor a new CannotReadScriptException.
32-
* @param resource the resource that could not be read from
32+
* Construct a new {@code CannotReadScriptException}.
33+
* @param resource the resource that cannot be read from
3334
* @param cause the underlying cause of the resource access failure
3435
*/
3536
public CannotReadScriptException(EncodedResource resource, Throwable cause) {

org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -23,16 +23,18 @@
2323
* failed when executing it against the target database.
2424
*
2525
* @author Juergen Hoeller
26+
* @author Sam Brannen
2627
* @since 3.0.5
2728
*/
29+
@SuppressWarnings("serial")
2830
public class ScriptStatementFailedException extends RuntimeException {
2931

3032
/**
31-
* Constructor a new ScriptStatementFailedException.
33+
* Construct a new {@code ScriptStatementFailedException}.
3234
* @param statement the actual SQL statement that failed
3335
* @param lineNumber the line number in the SQL script
34-
* @param resource the resource that could not be read from
35-
* @param cause the underlying cause of the resource access failure
36+
* @param resource the resource from which the SQL statement was read
37+
* @param cause the underlying cause of the failure
3638
*/
3739
public ScriptStatementFailedException(String statement, int lineNumber, EncodedResource resource, Throwable cause) {
3840
super("Failed to execute SQL script statement at line " + lineNumber +

org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.dao.DataAccessException;
3131
import org.springframework.dao.DataAccessResourceFailureException;
3232
import org.springframework.jdbc.core.JdbcTemplate;
33+
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
3334
import org.springframework.util.StringUtils;
3435

3536
/**
@@ -47,6 +48,10 @@ public class JdbcTestUtils {
4748

4849
private static final Log logger = LogFactory.getLog(JdbcTestUtils.class);
4950

51+
private static final String DEFAULT_COMMENT_PREFIX = "--";
52+
53+
private static final char DEFAULT_STATEMENT_SEPARATOR = ';';
54+
5055

5156
/**
5257
* Count the rows in the given table.
@@ -124,10 +129,10 @@ public static void dropTables(JdbcTemplate jdbcTemplate, String... tableNames) {
124129
* exception in the event of an error
125130
* @throws DataAccessException if there is an error executing a statement
126131
* and {@code continueOnError} is {@code false}
132+
* @see ResourceDatabasePopulator
127133
*/
128134
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
129135
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
130-
131136
Resource resource = resourceLoader.getResource(sqlResourcePath);
132137
executeSqlScript(jdbcTemplate, resource, continueOnError);
133138
}
@@ -145,10 +150,10 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader re
145150
* exception in the event of an error
146151
* @throws DataAccessException if there is an error executing a statement
147152
* and {@code continueOnError} is {@code false}
153+
* @see ResourceDatabasePopulator
148154
*/
149155
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
150156
throws DataAccessException {
151-
152157
executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
153158
}
154159

@@ -164,6 +169,7 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource
164169
* exception in the event of an error
165170
* @throws DataAccessException if there is an error executing a statement
166171
* and {@code continueOnError} is {@code false}
172+
* @see ResourceDatabasePopulator
167173
*/
168174
public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
169175
throws DataAccessException {
@@ -177,12 +183,14 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource r
177183
try {
178184
reader = new LineNumberReader(resource.getReader());
179185
String script = readScript(reader);
180-
char delimiter = ';';
186+
char delimiter = DEFAULT_STATEMENT_SEPARATOR;
181187
if (!containsSqlScriptDelimiters(script, delimiter)) {
182188
delimiter = '\n';
183189
}
184190
splitSqlScript(script, delimiter, statements);
191+
int lineNumber = 0;
185192
for (String statement : statements) {
193+
lineNumber++;
186194
try {
187195
int rowsAffected = jdbcTemplate.update(statement);
188196
if (logger.isDebugEnabled()) {
@@ -192,7 +200,8 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource r
192200
catch (DataAccessException ex) {
193201
if (continueOnError) {
194202
if (logger.isWarnEnabled()) {
195-
logger.warn("SQL statement [" + statement + "] failed", ex);
203+
logger.warn("Failed to execute SQL script statement at line " + lineNumber
204+
+ " of resource " + resource + ": " + statement, ex);
196205
}
197206
}
198207
else {
@@ -213,24 +222,40 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource r
213222
if (reader != null) {
214223
reader.close();
215224
}
216-
} catch (IOException ex) {
225+
}
226+
catch (IOException ex) {
217227
// ignore
218228
}
219229
}
220230
}
221231

222232
/**
223-
* Read a script from the provided {@code LineNumberReader} and build a
224-
* {@code String} containing the lines.
233+
* Read a script from the provided {@code LineNumberReader}, using
234+
* "{@code --}" as the comment prefix, and build a {@code String} containing
235+
* the lines.
225236
* @param lineNumberReader the {@code LineNumberReader} containing the script
226237
* to be processed
227238
* @return a {@code String} containing the script lines
239+
* @see #readScript(LineNumberReader, String)
228240
*/
229241
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
242+
return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX);
243+
}
244+
245+
/**
246+
* Read a script from the provided {@code LineNumberReader}, using the supplied
247+
* comment prefix, and build a {@code String} containing the lines.
248+
* @param lineNumberReader the {@code LineNumberReader} containing the script
249+
* to be processed
250+
* @param commentPrefix the line prefix that identifies comments in the SQL script
251+
* @return a {@code String} containing the script lines
252+
*/
253+
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix) throws IOException {
230254
String currentStatement = lineNumberReader.readLine();
231255
StringBuilder scriptBuilder = new StringBuilder();
232256
while (currentStatement != null) {
233-
if (StringUtils.hasText(currentStatement)) {
257+
if (StringUtils.hasText(currentStatement)
258+
&& (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) {
234259
if (scriptBuilder.length() > 0) {
235260
scriptBuilder.append('\n');
236261
}
@@ -270,26 +295,71 @@ public static boolean containsSqlScriptDelimiters(String script, char delim) {
270295
* @param statements the list that will contain the individual statements
271296
*/
272297
public static void splitSqlScript(String script, char delim, List<String> statements) {
298+
splitSqlScript(script, "" + delim, statements);
299+
}
300+
301+
/**
302+
* Split an SQL script into separate statements delimited with the provided delimiter
303+
* character. Each individual statement will be added to the provided {@code List}.
304+
* @param script the SQL script
305+
* @param delim character delimiting each statement &mdash; typically a ';' character
306+
* @param statements the List that will contain the individual statements
307+
*/
308+
private static void splitSqlScript(String script, String delim, List<String> statements) {
273309
StringBuilder sb = new StringBuilder();
274310
boolean inLiteral = false;
311+
boolean inEscape = false;
275312
char[] content = script.toCharArray();
276313
for (int i = 0; i < script.length(); i++) {
277-
if (content[i] == '\'') {
314+
char c = content[i];
315+
if (inEscape) {
316+
inEscape = false;
317+
sb.append(c);
318+
continue;
319+
}
320+
// MySQL style escapes
321+
if (c == '\\') {
322+
inEscape = true;
323+
sb.append(c);
324+
continue;
325+
}
326+
if (c == '\'') {
278327
inLiteral = !inLiteral;
279328
}
280-
if (content[i] == delim && !inLiteral) {
281-
if (sb.length() > 0) {
282-
statements.add(sb.toString());
283-
sb = new StringBuilder();
329+
if (!inLiteral) {
330+
if (startsWithDelimiter(script, i, delim)) {
331+
if (sb.length() > 0) {
332+
statements.add(sb.toString());
333+
sb = new StringBuilder();
334+
}
335+
i += delim.length() - 1;
336+
continue;
337+
}
338+
else if (c == '\n' || c == '\t') {
339+
c = ' ';
284340
}
285341
}
286-
else {
287-
sb.append(content[i]);
288-
}
342+
sb.append(c);
289343
}
290-
if (sb.length() > 0) {
344+
if (StringUtils.hasText(sb)) {
291345
statements.add(sb.toString());
292346
}
293347
}
294348

349+
/**
350+
* Return whether the substring of a given source {@link String} starting at the
351+
* given index starts with the given delimiter.
352+
* @param source the source {@link String} to inspect
353+
* @param startIndex the index to look for the delimiter
354+
* @param delim the delimiter to look for
355+
*/
356+
private static boolean startsWithDelimiter(String source, int startIndex, String delim) {
357+
int endIndex = startIndex + delim.length();
358+
if (source.length() < endIndex) {
359+
// String is too short to contain the delimiter
360+
return false;
361+
}
362+
return source.substring(startIndex, endIndex).equals(delim);
363+
}
364+
295365
}

org.springframework.test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 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.
@@ -16,18 +16,21 @@
1616

1717
package org.springframework.test.jdbc;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertTrue;
19+
import static org.junit.Assert.*;
2120

21+
import java.io.LineNumberReader;
2222
import java.util.ArrayList;
2323
import java.util.List;
2424

2525
import org.junit.Test;
26+
import org.springframework.core.io.ClassPathResource;
27+
import org.springframework.core.io.support.EncodedResource;
2628

2729
/**
2830
* Unit tests for {@link JdbcTestUtils}.
2931
*
3032
* @author Thomas Risberg
33+
* @author Sam Brannen
3134
* @since 2.5.4
3235
*/
3336
public class JdbcTestUtilsTests {
@@ -45,12 +48,29 @@ public void containsDelimiters() {
4548

4649
@Test
4750
public void splitSqlScriptDelimitedWithSemicolon() {
48-
String statement1 = "insert into customer (id, name) \n"
49-
+ "values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
50-
String statement2 = "insert into orders(id, order_date, customer_id) \n" + "values (1, '2008-01-02', 2)";
51-
String statement3 = "insert into orders(id, order_date, customer_id) " + "values (1, '2008-01-02', 2)";
51+
String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
52+
String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
53+
String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)";
54+
String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
55+
String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
56+
String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
5257
char delim = ';';
53-
String script = statement1 + delim + statement2 + delim + statement3;
58+
String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim;
59+
List<String> statements = new ArrayList<String>();
60+
JdbcTestUtils.splitSqlScript(script, delim, statements);
61+
assertEquals("wrong number of statements", 3, statements.size());
62+
assertEquals("statement 1 not split correctly", cleanedStatement1, statements.get(0));
63+
assertEquals("statement 2 not split correctly", cleanedStatement2, statements.get(1));
64+
assertEquals("statement 3 not split correctly", cleanedStatement3, statements.get(2));
65+
}
66+
67+
@Test
68+
public void splitSqlScriptDelimitedWithNewLine() {
69+
String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
70+
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
71+
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
72+
char delim = '\n';
73+
String script = statement1 + delim + statement2 + delim + statement3 + delim;
5474
List<String> statements = new ArrayList<String>();
5575
JdbcTestUtils.splitSqlScript(script, delim, statements);
5676
assertEquals("wrong number of statements", 3, statements.size());
@@ -60,14 +80,22 @@ public void splitSqlScriptDelimitedWithSemicolon() {
6080
}
6181

6282
@Test
63-
public void splitSqlScriptDelimitedWithNewLine() {
64-
String statement1 = "insert into customer (id, name) " + "values (1, 'Rod ; Johnson'), (2, 'Adrian ; Collier')";
65-
String statement2 = "insert into orders(id, order_date, customer_id) " + "values (1, '2008-01-02', 2)";
66-
String statement3 = "insert into orders(id, order_date, customer_id) " + "values (1, '2008-01-02', 2)";
67-
char delim = '\n';
68-
String script = statement1 + delim + statement2 + delim + statement3;
83+
public void readAndSplitScriptContainingComments() throws Exception {
84+
85+
EncodedResource resource = new EncodedResource(new ClassPathResource("test-data-with-comments.sql", getClass()));
86+
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
87+
88+
String script = JdbcTestUtils.readScript(lineNumberReader);
89+
assertFalse("script should not contain --", script.contains("--"));
90+
91+
char delim = ';';
6992
List<String> statements = new ArrayList<String>();
7093
JdbcTestUtils.splitSqlScript(script, delim, statements);
94+
95+
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
96+
String statement2 = " insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
97+
String statement3 = " insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
98+
7199
assertEquals("wrong number of statements", 3, statements.size());
72100
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
73101
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
insert into customer (id, name)
2+
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
3+
-- This is also a comment.
4+
insert into orders(id, order_date, customer_id)
5+
values (1, '2008-01-02', 2);
6+
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);

0 commit comments

Comments
 (0)