提交 aa16caa2 编写于 作者: S Sam Brannen

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
上级 d0f687f0
......@@ -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 &mdash; 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
......
-- 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
);--
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册