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:
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 — 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 — 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 — 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 — 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 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user