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 Juergen Hoeller
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class ResourceDatabasePopulator implements DatabasePopulator {
|
public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
|
@ -54,7 +55,6 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(ResourceDatabasePopulator.class);
|
private static final Log logger = LogFactory.getLog(ResourceDatabasePopulator.class);
|
||||||
|
|
||||||
|
|
||||||
private List<Resource> scripts = new ArrayList<Resource>();
|
private List<Resource> scripts = new ArrayList<Resource>();
|
||||||
|
|
||||||
private String sqlScriptEncoding;
|
private String sqlScriptEncoding;
|
||||||
|
@ -127,7 +127,6 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
this.ignoreFailedDrops = ignoreFailedDrops;
|
this.ignoreFailedDrops = ignoreFailedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void populate(Connection connection) throws SQLException {
|
public void populate(Connection connection) throws SQLException {
|
||||||
for (Resource script : this.scripts) {
|
for (Resource script : this.scripts) {
|
||||||
executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops);
|
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 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}
|
* @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,
|
private void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
|
||||||
boolean continueOnError, boolean ignoreFailedDrops) throws SQLException {
|
boolean ignoreFailedDrops) throws SQLException {
|
||||||
|
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
logger.info("Executing SQL script from " + resource);
|
logger.info("Executing SQL script from " + resource);
|
||||||
|
@ -175,7 +174,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
delimiter = "\n";
|
delimiter = "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splitSqlScript(script, delimiter, statements);
|
splitSqlScript(script, delimiter, this.commentPrefix, statements);
|
||||||
int lineNumber = 0;
|
int lineNumber = 0;
|
||||||
Statement stmt = connection.createStatement();
|
Statement stmt = connection.createStatement();
|
||||||
try {
|
try {
|
||||||
|
@ -192,8 +191,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
|
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
|
||||||
if (continueOnError || (dropStatement && ignoreFailedDrops)) {
|
if (continueOnError || (dropStatement && ignoreFailedDrops)) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Failed to execute SQL script statement at line " + lineNumber +
|
logger.debug("Failed to execute SQL script statement at line " + lineNumber
|
||||||
" of resource " + resource + ": " + statement, ex);
|
+ " of resource " + resource + ": " + statement, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -239,7 +238,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
}
|
}
|
||||||
maybeAddSeparatorToScript(scriptBuilder);
|
maybeAddSeparatorToScript(scriptBuilder);
|
||||||
return scriptBuilder.toString();
|
return scriptBuilder.toString();
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
lnr.close();
|
lnr.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
if (trimmed.length() == this.separator.length()) {
|
if (trimmed.length() == this.separator.length()) {
|
||||||
return;
|
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()) {
|
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
|
||||||
scriptBuilder.append(this.separator.substring(trimmed.length()));
|
scriptBuilder.append(this.separator.substring(trimmed.length()));
|
||||||
}
|
}
|
||||||
|
@ -270,7 +271,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
if (content[i] == '\'') {
|
if (content[i] == '\'') {
|
||||||
inLiteral = !inLiteral;
|
inLiteral = !inLiteral;
|
||||||
}
|
}
|
||||||
if (!inLiteral && startsWithDelimiter(script, i, delim)) {
|
if (!inLiteral && script.startsWith(delim, i)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,29 +279,18 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the substring of a given source {@link String} starting at the
|
* Split an SQL script into separate statements delimited by the provided delimiter
|
||||||
* given index starts with the given delimiter.
|
* string. Each individual statement will be added to the provided {@code List}.
|
||||||
* @param source the source {@link String} to inspect
|
* <p>Within a statement, the provided {@code commentPrefix} will be honored;
|
||||||
* @param startIndex the index to look for the delimiter
|
* any text beginning with the comment prefix and extending to the end of the
|
||||||
* @param delim the delimiter to look for
|
* line will be omitted from the statement. In addition, multiple adjacent
|
||||||
*/
|
* whitespace characters will be collapsed into a single space.
|
||||||
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}.
|
|
||||||
* @param script the SQL script
|
* @param script the SQL script
|
||||||
* @param delim character delimiting each statement (typically a ';' character)
|
* @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
|
* @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();
|
StringBuilder sb = new StringBuilder();
|
||||||
boolean inLiteral = false;
|
boolean inLiteral = false;
|
||||||
boolean inEscape = false;
|
boolean inEscape = false;
|
||||||
|
@ -322,7 +312,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
inLiteral = !inLiteral;
|
inLiteral = !inLiteral;
|
||||||
}
|
}
|
||||||
if (!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) {
|
if (sb.length() > 0) {
|
||||||
statements.add(sb.toString());
|
statements.add(sb.toString());
|
||||||
sb = new StringBuilder();
|
sb = new StringBuilder();
|
||||||
|
@ -330,9 +321,28 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
i += delim.length() - 1;
|
i += delim.length() - 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (c == '\n' || c == '\t') {
|
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 = ' ';
|
c = ' ';
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sb.append(c);
|
sb.append(c);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,11 @@ public class DatabasePopulatorTests {
|
||||||
assertEquals(name, jdbcTemplate.queryForObject("select NAME from T_TEST", String.class));
|
assertEquals(name, jdbcTemplate.queryForObject("select NAME from T_TEST", String.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertUsersDatabaseCreated() {
|
private void assertUsersDatabaseCreated(String... lastNames) {
|
||||||
assertEquals("Sam", jdbcTemplate.queryForObject("select first_name from users where last_name = 'Brannen'",
|
for (String lastName : lastNames) {
|
||||||
String.class));
|
assertEquals("Did not find user with last name [" + lastName + "].", 1,
|
||||||
|
jdbcTemplate.queryForInt("select count(0) from users where last_name = ?", lastName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -73,7 +75,6 @@ public class DatabasePopulatorTests {
|
||||||
db.shutdown();
|
db.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuildWithCommentsAndFailedDrop() throws Exception {
|
public void testBuildWithCommentsAndFailedDrop() throws Exception {
|
||||||
databasePopulator.addScript(resourceLoader.getResource("db-schema-failed-drop-comments.sql"));
|
databasePopulator.addScript(resourceLoader.getResource("db-schema-failed-drop-comments.sql"));
|
||||||
|
@ -215,7 +216,22 @@ public class DatabasePopulatorTests {
|
||||||
connection.close();
|
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
|
@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