Skip to content

Commit d0f687f

Browse files
committed
Support comments in statements in JdbcTestUtils
Prior to this commit, executing an SQL script with JdbcTestUtils would fail if a statement in the script contained a line comment within the statement. This commit ensures that standard SQL comments (i.e., any text beginning with two hyphens and extending to the end of the line) are properly omitted from the statement before executing it. In addition, multiple adjacent whitespace characters within a statement but outside a literal are now collapsed into a single space. Issue: SPR-9982
1 parent d1a6cee commit d0f687f

File tree

3 files changed

+65
-33
lines changed

3 files changed

+65
-33
lines changed

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

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public static void dropTables(JdbcTemplate jdbcTemplate, String... tableNames) {
120120
/**
121121
* Execute the given SQL script.
122122
* <p>The script will typically be loaded from the classpath. There should
123-
* be one statement per line. Any semicolons will be removed.
123+
* be one statement per line. Any semicolons and line comments will be removed.
124124
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
125125
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
126126
* @param resourceLoader the resource loader with which to load the SQL script
@@ -130,6 +130,7 @@ public static void dropTables(JdbcTemplate jdbcTemplate, String... tableNames) {
130130
* @throws DataAccessException if there is an error executing a statement
131131
* and {@code continueOnError} is {@code false}
132132
* @see ResourceDatabasePopulator
133+
* @see #executeSqlScript(JdbcTemplate, Resource, boolean)
133134
*/
134135
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
135136
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
@@ -142,7 +143,8 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader re
142143
* <p>The script will typically be loaded from the classpath. Statements
143144
* should be delimited with a semicolon. If statements are not delimited with
144145
* a semicolon then there should be one statement per line. Statements are
145-
* allowed to span lines only if they are delimited with a semicolon.
146+
* allowed to span lines only if they are delimited with a semicolon. Any
147+
* line comments will be removed.
146148
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
147149
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
148150
* @param resource the resource to load the SQL script from
@@ -151,6 +153,7 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader re
151153
* @throws DataAccessException if there is an error executing a statement
152154
* and {@code continueOnError} is {@code false}
153155
* @see ResourceDatabasePopulator
156+
* @see #executeSqlScript(JdbcTemplate, EncodedResource, boolean)
154157
*/
155158
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
156159
throws DataAccessException {
@@ -160,7 +163,7 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource
160163
/**
161164
* Execute the given SQL script.
162165
* <p>The script will typically be loaded from the classpath. There should
163-
* be one statement per line. Any semicolons will be removed.
166+
* be one statement per line. Any semicolons and line comments will be removed.
164167
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
165168
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
166169
* @param resource the resource (potentially associated with a specific encoding)
@@ -245,9 +248,12 @@ public static String readScript(LineNumberReader lineNumberReader) throws IOExce
245248
/**
246249
* Read a script from the provided {@code LineNumberReader}, using the supplied
247250
* comment prefix, and build a {@code String} containing the lines.
251+
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
252+
* results; however, line comments anywhere else &mdash; for example, within
253+
* a statement &mdash; will be included in the results.
248254
* @param lineNumberReader the {@code LineNumberReader} containing the script
249255
* to be processed
250-
* @param commentPrefix the line prefix that identifies comments in the SQL script
256+
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash; typically "--"
251257
* @return a {@code String} containing the script lines
252258
*/
253259
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix) throws IOException {
@@ -287,25 +293,35 @@ public static boolean containsSqlScriptDelimiters(String script, char delim) {
287293
}
288294

289295
/**
290-
* Split an SQL script into separate statements delimited with the provided
296+
* Split an SQL script into separate statements delimited by the provided
291297
* delimiter character. Each individual statement will be added to the
292298
* provided <code>List</code>.
299+
* <p>Within a statement, "{@code --}" will be used as the comment prefix;
300+
* any text beginning with the comment prefix and extending to the end of
301+
* the line will be omitted from the statement. In addition, multiple adjacent
302+
* whitespace characters will be collapsed into a single space.
293303
* @param script the SQL script
294304
* @param delim character delimiting each statement &mdash; typically a ';' character
295305
* @param statements the list that will contain the individual statements
296306
*/
297307
public static void splitSqlScript(String script, char delim, List<String> statements) {
298-
splitSqlScript(script, "" + delim, statements);
308+
splitSqlScript(script, "" + delim, DEFAULT_COMMENT_PREFIX, statements);
299309
}
300310

301311
/**
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}.
312+
* Split an SQL script into separate statements delimited by the provided
313+
* delimiter string. Each individual statement will be added to the provided
314+
* {@code List}.
315+
* <p>Within a statement, the provided {@code commentPrefix} will be honored;
316+
* any text beginning with the comment prefix and extending to the end of the
317+
* line will be omitted from the statement. In addition, multiple adjacent
318+
* whitespace characters will be collapsed into a single space.
304319
* @param script the SQL script
305320
* @param delim character delimiting each statement &mdash; typically a ';' character
321+
* @param commentPrefix the prefix that identifies line comments in the SQL script &mdash; typically "--"
306322
* @param statements the List that will contain the individual statements
307323
*/
308-
private static void splitSqlScript(String script, String delim, List<String> statements) {
324+
private static void splitSqlScript(String script, String delim, String commentPrefix, List<String> statements) {
309325
StringBuilder sb = new StringBuilder();
310326
boolean inLiteral = false;
311327
boolean inEscape = false;
@@ -327,16 +343,36 @@ private static void splitSqlScript(String script, String delim, List<String> sta
327343
inLiteral = !inLiteral;
328344
}
329345
if (!inLiteral) {
330-
if (startsWithDelimiter(script, i, delim)) {
346+
if (script.startsWith(delim, i)) {
347+
// we've reached the end of the current statement
331348
if (sb.length() > 0) {
332349
statements.add(sb.toString());
333350
sb = new StringBuilder();
334351
}
335352
i += delim.length() - 1;
336353
continue;
337354
}
338-
else if (c == '\n' || c == '\t') {
339-
c = ' ';
355+
else if (script.startsWith(commentPrefix, i)) {
356+
// skip over any content from the start of the comment to the EOL
357+
int indexOfNextNewline = script.indexOf("\n", i);
358+
if (indexOfNextNewline > i) {
359+
i = indexOfNextNewline;
360+
continue;
361+
}
362+
else {
363+
// if there's no newline after the comment, we must be at the end
364+
// of the script, so stop here.
365+
break;
366+
}
367+
}
368+
else if (c == ' ' || c == '\n' || c == '\t') {
369+
// avoid multiple adjacent whitespace characters
370+
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
371+
c = ' ';
372+
}
373+
else {
374+
continue;
375+
}
340376
}
341377
}
342378
sb.append(c);
@@ -346,20 +382,4 @@ else if (c == '\n' || c == '\t') {
346382
}
347383
}
348384

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-
365385
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,22 @@ public void readAndSplitScriptContainingComments() throws Exception {
8686
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
8787

8888
String script = JdbcTestUtils.readScript(lineNumberReader);
89-
assertFalse("script should not contain --", script.contains("--"));
9089

9190
char delim = ';';
9291
List<String> statements = new ArrayList<String>();
9392
JdbcTestUtils.splitSqlScript(script, delim, statements);
9493

9594
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)";
95+
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
96+
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
97+
// Statement 4 addresses the error described in SPR-9982.
98+
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
9899

99-
assertEquals("wrong number of statements", 3, statements.size());
100+
assertEquals("wrong number of statements", 4, statements.size());
100101
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
101102
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
102103
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
104+
assertEquals("statement 4 not split correctly", statement4, statements.get(3));
103105
}
104106

105107
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1+
-- The next comment line has no text after the '--' prefix.
2+
--
3+
-- The next comment line starts with a space.
4+
-- x, y, z...
5+
16
insert into customer (id, name)
27
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
38
-- This is also a comment.
49
insert into orders(id, order_date, customer_id)
510
values (1, '2008-01-02', 2);
6-
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
11+
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
12+
INSERT INTO persons( person_id--
13+
, name)
14+
VALUES( 1 -- person_id
15+
, 'Name' --name
16+
);--

0 commit comments

Comments
 (0)