Support comments in statements in RDbPopulator
Prior to this commit, executing an SQL script via ResourceDatabasePopulator 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-10075
This commit is contained in:
parent
d0f687f028
commit
aa16caa2ac
|
@ -44,6 +44,7 @@ import org.springframework.util.StringUtils;
|
|||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Oliver Gierke
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
|
@ -54,7 +55,6 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
|
||||
private static final Log logger = LogFactory.getLog(ResourceDatabasePopulator.class);
|
||||
|
||||
|
||||
private List<Resource> scripts = new ArrayList<Resource>();
|
||||
|
||||
private String sqlScriptEncoding;
|
||||
|
@ -127,7 +127,6 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
this.ignoreFailedDrops = ignoreFailedDrops;
|
||||
}
|
||||
|
||||
|
||||
public void populate(Connection connection) throws SQLException {
|
||||
for (Resource script : this.scripts) {
|
||||
executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops);
|
||||
|
@ -153,8 +152,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
* @param continueOnError whether or not to continue without throwing an exception in the event of an error
|
||||
* @param ignoreFailedDrops whether of not to continue in the event of specifically an error on a {@code DROP}
|
||||
*/
|
||||
private void executeSqlScript(Connection connection, EncodedResource resource,
|
||||
boolean continueOnError, boolean ignoreFailedDrops) throws SQLException {
|
||||
private void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
|
||||
boolean ignoreFailedDrops) throws SQLException {
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Executing SQL script from " + resource);
|
||||
|
@ -175,7 +174,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
delimiter = "\n";
|
||||
}
|
||||
}
|
||||
splitSqlScript(script, delimiter, statements);
|
||||
splitSqlScript(script, delimiter, this.commentPrefix, statements);
|
||||
int lineNumber = 0;
|
||||
Statement stmt = connection.createStatement();
|
||||
try {
|
||||
|
@ -192,8 +191,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
|
||||
if (continueOnError || (dropStatement && ignoreFailedDrops)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to execute SQL script statement at line " + lineNumber +
|
||||
" of resource " + resource + ": " + statement, ex);
|
||||
logger.debug("Failed to execute SQL script statement at line " + lineNumber
|
||||
+ " of resource " + resource + ": " + statement, ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -239,7 +238,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
}
|
||||
maybeAddSeparatorToScript(scriptBuilder);
|
||||
return scriptBuilder.toString();
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
lnr.close();
|
||||
}
|
||||
}
|
||||
|
@ -252,7 +252,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
if (trimmed.length() == this.separator.length()) {
|
||||
return;
|
||||
}
|
||||
// separator ends in whitespace, so we might want to see if the script is trying to end the same way
|
||||
// separator ends in whitespace, so we might want to see if the script is trying
|
||||
// to end the same way
|
||||
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
|
||||
scriptBuilder.append(this.separator.substring(trimmed.length()));
|
||||
}
|
||||
|
@ -270,7 +271,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
if (content[i] == '\'') {
|
||||
inLiteral = !inLiteral;
|
||||
}
|
||||
if (!inLiteral && startsWithDelimiter(script, i, delim)) {
|
||||
if (!inLiteral && script.startsWith(delim, i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -278,29 +279,18 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (typically a ';' character)
|
||||
* @param commentPrefix the prefix that identifies line comments in the SQL script — typically "--"
|
||||
* @param statements the List that will contain the individual statements
|
||||
*/
|
||||
private void splitSqlScript(String script, String delim, List<String> statements) {
|
||||
private void splitSqlScript(String script, String delim, String commentPrefix, List<String> statements) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean inLiteral = false;
|
||||
boolean inEscape = false;
|
||||
|
@ -322,7 +312,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
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();
|
||||
|
@ -330,8 +321,27 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
|||
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);
|
||||
|
|
|
@ -59,9 +59,11 @@ public class DatabasePopulatorTests {
|
|||
assertEquals(name, jdbcTemplate.queryForObject("select NAME from T_TEST", String.class));
|
||||
}
|
||||
|
||||
private void assertUsersDatabaseCreated() {
|
||||
assertEquals("Sam", jdbcTemplate.queryForObject("select first_name from users where last_name = 'Brannen'",
|
||||
String.class));
|
||||
private void assertUsersDatabaseCreated(String... lastNames) {
|
||||
for (String lastName : lastNames) {
|
||||
assertEquals("Did not find user with last name [" + lastName + "].", 1,
|
||||
jdbcTemplate.queryForInt("select count(0) from users where last_name = ?", lastName));
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -73,7 +75,6 @@ public class DatabasePopulatorTests {
|
|||
db.shutdown();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBuildWithCommentsAndFailedDrop() throws Exception {
|
||||
databasePopulator.addScript(resourceLoader.getResource("db-schema-failed-drop-comments.sql"));
|
||||
|
@ -215,7 +216,22 @@ public class DatabasePopulatorTests {
|
|||
connection.close();
|
||||
}
|
||||
|
||||
assertUsersDatabaseCreated();
|
||||
assertUsersDatabaseCreated("Brannen");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptWithCommentsWithinStatements() throws Exception {
|
||||
databasePopulator.addScript(resourceLoader.getResource("users-schema.sql"));
|
||||
databasePopulator.addScript(resourceLoader.getResource("users-data-with-comments.sql"));
|
||||
Connection connection = db.getConnection();
|
||||
try {
|
||||
databasePopulator.populate(connection);
|
||||
}
|
||||
finally {
|
||||
connection.close();
|
||||
}
|
||||
|
||||
assertUsersDatabaseCreated("Brannen", "Hoeller");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
-- The next comment line has no text after the '--' prefix.
|
||||
--
|
||||
-- The next comment line starts with a space.
|
||||
-- x, y, z...
|
||||
|
||||
INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller');
|
||||
-- This is also a comment.
|
||||
INSERT INTO
|
||||
users(first_name, last_name)
|
||||
VALUES( 'Sam' -- first_name
|
||||
, 'Brannen' -- last_name
|
||||
);--
|
Loading…
Reference in New Issue