Skip to content

Commit

Permalink
Support comments in statements in JdbcTestUtils
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Dec 5, 2012
1 parent d1a6cee commit d0f687f
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 33 deletions.
Expand Up @@ -120,7 +120,7 @@ public static void dropTables(JdbcTemplate jdbcTemplate, String... tableNames) {
/**
* Execute the given SQL script.
* <p>The script will typically be loaded from the classpath. There should
* be one statement per line. Any semicolons will be removed.
* be one statement per line. Any semicolons and line comments will be removed.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
* @param resourceLoader the resource loader with which to load the SQL script
Expand All @@ -130,6 +130,7 @@ public static void dropTables(JdbcTemplate jdbcTemplate, String... tableNames) {
* @throws DataAccessException if there is an error executing a statement
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @see #executeSqlScript(JdbcTemplate, Resource, boolean)
*/
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Expand All @@ -142,7 +143,8 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader re
* <p>The script will typically be loaded from the classpath. Statements
* should be delimited with a semicolon. If statements are not delimited with
* a semicolon then there should be one statement per line. Statements are
* allowed to span lines only if they are delimited with a semicolon.
* allowed to span lines only if they are delimited with a semicolon. Any
* line comments will be removed.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
* @param resource the resource to load the SQL script from
Expand All @@ -151,6 +153,7 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader re
* @throws DataAccessException if there is an error executing a statement
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @see #executeSqlScript(JdbcTemplate, EncodedResource, boolean)
*/
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
throws DataAccessException {
Expand All @@ -160,7 +163,7 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource
/**
* Execute the given SQL script.
* <p>The script will typically be loaded from the classpath. There should
* be one statement per line. Any semicolons will be removed.
* be one statement per line. Any semicolons and line comments will be removed.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
* @param resource the resource (potentially associated with a specific encoding)
Expand Down Expand Up @@ -245,9 +248,12 @@ public static String readScript(LineNumberReader lineNumberReader) throws IOExce
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix, and build a {@code String} containing the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param commentPrefix the line prefix that identifies comments in the SQL script
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash; typically "--"
* @return a {@code String} containing the script lines
*/
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix) throws IOException {
Expand Down Expand Up @@ -287,25 +293,35 @@ public static boolean containsSqlScriptDelimiters(String script, char delim) {
}

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

/**
* Split an SQL script into separate statements delimited with the provided delimiter
* character. Each individual statement will be added to the provided {@code List}.
* Split an SQL script into separate statements delimited by the provided
* delimiter string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within a statement, the provided {@code commentPrefix} will be honored;
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the statement. In addition, multiple adjacent
* whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @param commentPrefix the prefix that identifies line comments in the SQL script &mdash; typically "--"
* @param statements the List that will contain the individual statements
*/
private static void splitSqlScript(String script, String delim, List<String> statements) {
private static void splitSqlScript(String script, String delim, String commentPrefix, List<String> statements) {
StringBuilder sb = new StringBuilder();
boolean inLiteral = false;
boolean inEscape = false;
Expand All @@ -327,16 +343,36 @@ private static void splitSqlScript(String script, String delim, List<String> sta
inLiteral = !inLiteral;
}
if (!inLiteral) {
if (startsWithDelimiter(script, i, delim)) {
if (script.startsWith(delim, i)) {
// we've reached the end of the current statement
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
i += delim.length() - 1;
continue;
}
else if (c == '\n' || c == '\t') {
c = ' ';
else if (script.startsWith(commentPrefix, i)) {
// skip over any content from the start of the comment to the EOL
int indexOfNextNewline = script.indexOf("\n", i);
if (indexOfNextNewline > i) {
i = indexOfNextNewline;
continue;
}
else {
// if there's no newline after the comment, we must be at the end
// of the script, so stop here.
break;
}
}
else if (c == ' ' || c == '\n' || c == '\t') {
// avoid multiple adjacent whitespace characters
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
c = ' ';
}
else {
continue;
}
}
}
sb.append(c);
Expand All @@ -346,20 +382,4 @@ else if (c == '\n' || c == '\t') {
}
}

/**
* Return whether the substring of a given source {@link String} starting at the
* given index starts with the given delimiter.
* @param source the source {@link String} to inspect
* @param startIndex the index to look for the delimiter
* @param delim the delimiter to look for
*/
private static boolean startsWithDelimiter(String source, int startIndex, String delim) {
int endIndex = startIndex + delim.length();
if (source.length() < endIndex) {
// String is too short to contain the delimiter
return false;
}
return source.substring(startIndex, endIndex).equals(delim);
}

}
Expand Up @@ -86,20 +86,22 @@ public void readAndSplitScriptContainingComments() throws Exception {
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());

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

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

String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
String statement2 = " insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = " insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
// Statement 4 addresses the error described in SPR-9982.
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";

assertEquals("wrong number of statements", 3, statements.size());
assertEquals("wrong number of statements", 4, statements.size());
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
assertEquals("statement 4 not split correctly", statement4, statements.get(3));
}

}
@@ -1,6 +1,16 @@
-- The next comment line has no text after the '--' prefix.
--
-- The next comment line starts with a space.
-- x, y, z...

insert into customer (id, name)
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
-- This is also a comment.
insert into orders(id, order_date, customer_id)
values (1, '2008-01-02', 2);
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
INSERT INTO persons( person_id--
, name)
VALUES( 1 -- person_id
, 'Name' --name
);--

0 comments on commit d0f687f

Please sign in to comment.