Support multi-line comments in SQL scripts

Prior to this commit neither ResourceDatabasePopulator nor
JdbcTestUtils properly supported multi-line comments (e.g., /* ... */).
Secondarily there has developed a significant amount of code
duplication in these two classes that has led to maintenance issues
over the years.

This commit addresses these issues as follows:

 - Common code has been extracted from ResourceDatabasePopulator and
   JdbcTestUtils and moved to a new ScriptUtils class in the
   spring-jdbc module.

 - Relevant test cases have been migrated from JdbcTestUtilsTests to
   ScriptUtilsTests.

 - ScriptUtils.splitSqlScript() has been modified to ignore multi-line
   comments in scripts during processing.

 - ResourceDatabasePopulator supports configuration of the start and end
   delimiters for multi-line (block) comments.

 - A new test case was added to ScriptUtilsTests for the new multi-line
   comment support.

Issue: SPR-9531
This commit is contained in:
cbaldwin74
2014-03-06 01:38:38 -08:00
committed by Sam Brannen
parent 6b31074e4a
commit e5c17560db
10 changed files with 744 additions and 500 deletions

View File

@@ -182,6 +182,7 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @see #setSqlScriptEncoding
*/
@SuppressWarnings("deprecation")
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),

View File

@@ -173,6 +173,7 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @see #setSqlScriptEncoding
*/
@SuppressWarnings("deprecation")
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.test.jdbc;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
@@ -27,10 +26,11 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.jdbc.datasource.init.ScriptUtils.ScriptStatementExecutor;
import org.springframework.util.StringUtils;
/**
@@ -41,17 +41,13 @@ import org.springframework.util.StringUtils;
* @author Sam Brannen
* @author Juergen Hoeller
* @author Phillip Webb
* @author Chris Baldwin
* @since 2.5.4
*/
public class JdbcTestUtils {
private static final Log logger = LogFactory.getLog(JdbcTestUtils.class);
private static final String DEFAULT_COMMENT_PREFIX = "--";
private static final char DEFAULT_STATEMENT_SEPARATOR = ';';
/**
* Count the rows in the given table.
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
@@ -163,7 +159,10 @@ public class JdbcTestUtils {
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @see #executeSqlScript(JdbcTemplate, Resource, boolean)
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript(ScriptStatementExecutor, EncodedResource, boolean, boolean, String, String, String, String)}
*/
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = resourceLoader.getResource(sqlResourcePath);
@@ -186,7 +185,10 @@ public class JdbcTestUtils {
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @see #executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript(ScriptStatementExecutor, EncodedResource, boolean, boolean, String, String, String, String)}
*/
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
throws DataAccessException {
executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
@@ -205,65 +207,36 @@ public class JdbcTestUtils {
* @throws DataAccessException if there is an error executing a statement
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript(ScriptStatementExecutor, EncodedResource, boolean, boolean, String, String, String, String)}
*/
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
throws DataAccessException {
if (logger.isInfoEnabled()) {
logger.info("Executing SQL script from " + resource);
}
long startTime = System.currentTimeMillis();
List<String> statements = new LinkedList<String>();
LineNumberReader reader = null;
try {
reader = new LineNumberReader(resource.getReader());
String script = readScript(reader);
char delimiter = DEFAULT_STATEMENT_SEPARATOR;
if (!containsSqlScriptDelimiters(script, delimiter)) {
delimiter = '\n';
}
splitSqlScript(script, delimiter, statements);
int lineNumber = 0;
for (String statement : statements) {
lineNumber++;
try {
int rowsAffected = jdbcTemplate.update(statement);
if (logger.isDebugEnabled()) {
logger.debug(rowsAffected + " rows affected by SQL: " + statement);
}
}
catch (DataAccessException ex) {
if (continueOnError) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to execute SQL script statement at line " + lineNumber
+ " of resource " + resource + ": " + statement, ex);
}
}
else {
throw ex;
}
}
}
long elapsedTime = System.currentTimeMillis() - startTime;
if (logger.isInfoEnabled()) {
logger.info(String.format("Executed SQL script from %s in %s ms.", resource, elapsedTime));
}
}
catch (IOException ex) {
throw new DataAccessResourceFailureException("Failed to open SQL script from " + resource, ex);
}
finally {
try {
if (reader != null) {
reader.close();
}
}
catch (IOException ex) {
// ignore
}
}
ScriptUtils.executeSqlScript(new JdbcTemplateScriptStatementExecutor(jdbcTemplate), resource,
continueOnError, continueOnError, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
private static class JdbcTemplateScriptStatementExecutor implements ScriptStatementExecutor {
private JdbcTemplate jdbcTemplate;
public JdbcTemplateScriptStatementExecutor(JdbcTemplate jdbcTemplate) {
super();
this.jdbcTemplate = jdbcTemplate;
}
/* (non-Javadoc)
* @see org.springframework.jdbc.support.JdbcUtils.ScriptStatementExecutor#executeScriptStatement(java.lang.String)
*/
@Override
public int executeScriptStatement(String statement) throws DataAccessException {
return jdbcTemplate.update(statement);
}
}
/**
* Read a script from the provided {@code LineNumberReader}, using
* "{@code --}" as the comment prefix, and build a {@code String} containing
@@ -272,11 +245,14 @@ public class JdbcTestUtils {
* to be processed
* @return a {@code String} containing the script lines
* @see #readScript(LineNumberReader, String)
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#readScript(LineNumberReader)}
*/
@Deprecated
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX);
return ScriptUtils.readScript(lineNumberReader);
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix, and build a {@code String} containing the lines.
@@ -287,21 +263,12 @@ public class JdbcTestUtils {
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash; typically "--"
* @return a {@code String} containing the script lines
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#readScript(LineNumberReader, String, String)}
*/
@Deprecated
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix) throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if (StringUtils.hasText(currentStatement)
&& (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
scriptBuilder.append(currentStatement);
}
currentStatement = lineNumberReader.readLine();
}
return scriptBuilder.toString();
return ScriptUtils.readScript(lineNumberReader, commentPrefix, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
}
/**
@@ -309,21 +276,14 @@ public class JdbcTestUtils {
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @return {@code true} if the script contains the delimiter; {@code false} otherwise
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#containsSqlScriptDelimiters(String, char)}
*/
@Deprecated
public static boolean containsSqlScriptDelimiters(String script, char delim) {
boolean inLiteral = false;
char[] content = script.toCharArray();
for (int i = 0; i < script.length(); i++) {
if (content[i] == '\'') {
inLiteral = !inLiteral;
}
if (content[i] == delim && !inLiteral) {
return true;
}
}
return false;
return ScriptUtils.containsSqlScriptDelimiters(script, delim);
}
/**
* Split an SQL script into separate statements delimited by the provided
* delimiter character. Each individual statement will be added to the
@@ -335,83 +295,11 @@ public class JdbcTestUtils {
* @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
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#splitSqlScript(String, char, List)}
*/
@Deprecated
public static void splitSqlScript(String script, char delim, List<String> statements) {
splitSqlScript(script, "" + delim, DEFAULT_COMMENT_PREFIX, statements);
ScriptUtils.splitSqlScript(script, delim, statements);
}
/**
* 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, String commentPrefix, List<String> statements) {
StringBuilder sb = new StringBuilder();
boolean inLiteral = false;
boolean inEscape = false;
char[] content = script.toCharArray();
for (int i = 0; i < script.length(); i++) {
char c = content[i];
if (inEscape) {
inEscape = false;
sb.append(c);
continue;
}
// MySQL style escapes
if (c == '\\') {
inEscape = true;
sb.append(c);
continue;
}
if (c == '\'') {
inLiteral = !inLiteral;
}
if (!inLiteral) {
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 (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);
}
if (StringUtils.hasText(sb)) {
statements.add(sb.toString());
}
}
}