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:
parent
6b31074e4a
commit
e5c17560db
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,22 +16,18 @@
|
||||||
|
|
||||||
package org.springframework.jdbc.datasource.init;
|
package org.springframework.jdbc.datasource.init;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.LineNumberReader;
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.support.EncodedResource;
|
import org.springframework.core.io.support.EncodedResource;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.jdbc.UncategorizedSQLException;
|
||||||
|
import org.springframework.jdbc.datasource.init.ScriptUtils.ScriptStatementExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates a database from SQL scripts defined in external resources.
|
* Populates a database from SQL scripts defined in external resources.
|
||||||
|
|
@ -45,24 +41,22 @@ import org.springframework.util.StringUtils;
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
* @author Chris Baldwin
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public class ResourceDatabasePopulator implements DatabasePopulator {
|
public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
|
|
||||||
private static final String DEFAULT_COMMENT_PREFIX = "--";
|
|
||||||
|
|
||||||
private static final String DEFAULT_STATEMENT_SEPARATOR = ";";
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
private String separator;
|
private String separator = ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
|
||||||
|
|
||||||
private String commentPrefix = DEFAULT_COMMENT_PREFIX;
|
private String commentPrefix = ScriptUtils.DEFAULT_COMMENT_PREFIX;
|
||||||
|
|
||||||
|
private String blockCommentStartDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
|
||||||
|
|
||||||
|
private String blockCommentEndDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
|
||||||
|
|
||||||
private boolean continueOnError = false;
|
private boolean continueOnError = false;
|
||||||
|
|
||||||
|
|
@ -110,6 +104,24 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
this.commentPrefix = commentPrefix;
|
this.commentPrefix = commentPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the block comment start delimiter in the SQL script.
|
||||||
|
* Default is "/*"
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public void setBlockCommentStartDelimiter(String blockCommentStartDelimiter) {
|
||||||
|
this.blockCommentStartDelimiter = blockCommentStartDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the block comment end delimiter in the SQL script.
|
||||||
|
* Default is "*\/"
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public void setBlockCommentEndDelimiter(String blockCommentEndDelimiter) {
|
||||||
|
this.blockCommentEndDelimiter = blockCommentEndDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to indicate that all failures in SQL should be logged but not cause a failure.
|
* Flag to indicate that all failures in SQL should be logged but not cause a failure.
|
||||||
* Defaults to false.
|
* Defaults to false.
|
||||||
|
|
@ -131,8 +143,34 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void populate(Connection connection) throws SQLException {
|
public void populate(Connection connection) throws SQLException {
|
||||||
|
Statement statement = null;
|
||||||
|
try {
|
||||||
|
statement = connection.createStatement();
|
||||||
|
final Statement stmt = statement;
|
||||||
for (Resource script : this.scripts) {
|
for (Resource script : this.scripts) {
|
||||||
executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops);
|
ScriptUtils.executeSqlScript(
|
||||||
|
new ScriptStatementExecutor() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int executeScriptStatement(String statement) throws DataAccessException {
|
||||||
|
try {
|
||||||
|
stmt.execute(statement);
|
||||||
|
return stmt.getUpdateCount();
|
||||||
|
}
|
||||||
|
catch (SQLException e) {
|
||||||
|
throw new UncategorizedSQLException(getClass().getName(), statement, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops,
|
||||||
|
this.commentPrefix, this.separator, this.blockCommentStartDelimiter,
|
||||||
|
this.blockCommentEndDelimiter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (statement != null) {
|
||||||
|
statement.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,214 +182,4 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||||
return new EncodedResource(script, this.sqlScriptEncoding);
|
return new EncodedResource(script, this.sqlScriptEncoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the given SQL script.
|
|
||||||
* <p>The script will normally be loaded by classpath. There should be one statement
|
|
||||||
* per line. Any {@link #setSeparator(String) statement separators} will be removed.
|
|
||||||
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
|
|
||||||
* @param connection the JDBC Connection with which to perform JDBC operations
|
|
||||||
* @param resource the resource (potentially associated with a specific encoding) to load the SQL script from
|
|
||||||
* @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 {
|
|
||||||
|
|
||||||
if (logger.isInfoEnabled()) {
|
|
||||||
logger.info("Executing SQL script from " + resource);
|
|
||||||
}
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
List<String> statements = new LinkedList<String>();
|
|
||||||
String script;
|
|
||||||
try {
|
|
||||||
script = readScript(resource);
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
throw new CannotReadScriptException(resource, ex);
|
|
||||||
}
|
|
||||||
String delimiter = this.separator;
|
|
||||||
if (delimiter == null) {
|
|
||||||
delimiter = DEFAULT_STATEMENT_SEPARATOR;
|
|
||||||
if (!containsSqlScriptDelimiters(script, delimiter)) {
|
|
||||||
delimiter = "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
splitSqlScript(script, delimiter, this.commentPrefix, statements);
|
|
||||||
int lineNumber = 0;
|
|
||||||
Statement stmt = connection.createStatement();
|
|
||||||
try {
|
|
||||||
for (String statement : statements) {
|
|
||||||
lineNumber++;
|
|
||||||
try {
|
|
||||||
stmt.execute(statement);
|
|
||||||
int rowsAffected = stmt.getUpdateCount();
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(rowsAffected + " returned as updateCount for SQL: " + statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (SQLException ex) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new ScriptStatementFailedException(statement, lineNumber, resource, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {
|
|
||||||
stmt.close();
|
|
||||||
}
|
|
||||||
catch (Throwable ex) {
|
|
||||||
logger.debug("Could not close JDBC Statement", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
|
||||||
if (logger.isInfoEnabled()) {
|
|
||||||
logger.info("Done executing SQL script from " + resource + " in " + elapsedTime + " ms.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a script from the given resource and build a String containing the lines.
|
|
||||||
* @param resource the resource to be read
|
|
||||||
* @return {@code String} containing the script lines
|
|
||||||
* @throws IOException in case of I/O errors
|
|
||||||
*/
|
|
||||||
private String readScript(EncodedResource resource) throws IOException {
|
|
||||||
LineNumberReader lnr = new LineNumberReader(resource.getReader());
|
|
||||||
try {
|
|
||||||
String currentStatement = lnr.readLine();
|
|
||||||
StringBuilder scriptBuilder = new StringBuilder();
|
|
||||||
while (currentStatement != null) {
|
|
||||||
if (StringUtils.hasText(currentStatement) &&
|
|
||||||
(this.commentPrefix != null && !currentStatement.startsWith(this.commentPrefix))) {
|
|
||||||
if (scriptBuilder.length() > 0) {
|
|
||||||
scriptBuilder.append('\n');
|
|
||||||
}
|
|
||||||
scriptBuilder.append(currentStatement);
|
|
||||||
}
|
|
||||||
currentStatement = lnr.readLine();
|
|
||||||
}
|
|
||||||
maybeAddSeparatorToScript(scriptBuilder);
|
|
||||||
return scriptBuilder.toString();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
lnr.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeAddSeparatorToScript(StringBuilder scriptBuilder) {
|
|
||||||
if (this.separator == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String trimmed = this.separator.trim();
|
|
||||||
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
|
|
||||||
if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) {
|
|
||||||
scriptBuilder.append(this.separator.substring(trimmed.length()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the provided SQL script contain the specified delimiter?
|
|
||||||
* @param script the SQL script
|
|
||||||
* @param delim character delimiting each statement - typically a ';' character
|
|
||||||
*/
|
|
||||||
private boolean containsSqlScriptDelimiters(String script, String delim) {
|
|
||||||
boolean inLiteral = false;
|
|
||||||
char[] content = script.toCharArray();
|
|
||||||
for (int i = 0; i < script.length(); i++) {
|
|
||||||
if (content[i] == '\'') {
|
|
||||||
inLiteral = !inLiteral;
|
|
||||||
}
|
|
||||||
if (!inLiteral && script.startsWith(delim, i)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,375 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.jdbc.datasource.init;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.LineNumberReader;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.core.io.support.EncodedResource;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.jdbc.BadSqlGrammarException;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic utility methods for working with SQL scripts. Mainly for
|
||||||
|
* internal use within the framework.
|
||||||
|
*
|
||||||
|
* @author Chris Baldwin
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public abstract class ScriptUtils {
|
||||||
|
|
||||||
|
public static final String DEFAULT_COMMENT_PREFIX = "--";
|
||||||
|
|
||||||
|
public static final char DEFAULT_STATEMENT_SEPARATOR_CHAR = ';';
|
||||||
|
|
||||||
|
public static final String DEFAULT_STATEMENT_SEPARATOR = String.valueOf(DEFAULT_STATEMENT_SEPARATOR_CHAR);
|
||||||
|
|
||||||
|
public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = "/*";
|
||||||
|
|
||||||
|
public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/";
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split an SQL script into separate statements delimited by the provided
|
||||||
|
* delimiter character. Each individual statement will be added to the
|
||||||
|
* provided {@code List}.
|
||||||
|
* <p>Within a statement, "{@code --}" will be used as the comment prefix;
|
||||||
|
* 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 statements the list that will contain the individual statements
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static void splitSqlScript(String script, char delim, List<String> statements) {
|
||||||
|
splitSqlScript(script, String.valueOf(delim), DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
|
||||||
|
DEFAULT_BLOCK_COMMENT_END_DELIMITER, 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
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static void splitSqlScript(String script, String delim, String commentPrefix, String blockCommentOpen,
|
||||||
|
String blockCommentClose, 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 EOL, we must be at the end
|
||||||
|
// of the script, so stop here.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (script.startsWith(blockCommentOpen, i)) {
|
||||||
|
// skip over any block comments
|
||||||
|
int indexOfCommentClose = script.indexOf(blockCommentClose, i);
|
||||||
|
if (indexOfCommentClose > i) {
|
||||||
|
i = indexOfCommentClose + blockCommentClose.length() - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new BadSqlGrammarException("", script.substring(i),
|
||||||
|
new SQLException("Missing block comment end delimiter"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided {@code LineNumberReader}, using
|
||||||
|
* "{@code --}" as the comment prefix, and build a {@code String} containing
|
||||||
|
* the lines.
|
||||||
|
* @param lineNumberReader the {@code LineNumberReader} containing the script
|
||||||
|
* to be processed
|
||||||
|
* @return a {@code String} containing the script lines
|
||||||
|
* @see #readScript(LineNumberReader, String, String)
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
|
||||||
|
return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the given resource, using "{@code --}" as the comment prefix
|
||||||
|
* and "{@code ;} as the statement separator, and build a String containing the lines.
|
||||||
|
* @param resource the {@code EncodedResource} to be read
|
||||||
|
* @return {@code String} containing the script lines
|
||||||
|
* @throws IOException in case of I/O errors
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static String readScript(EncodedResource resource) throws IOException {
|
||||||
|
LineNumberReader lnr = new LineNumberReader(resource.getReader());
|
||||||
|
try {
|
||||||
|
return readScript(lnr, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lnr.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided resource, using the supplied
|
||||||
|
* comment prefix and statement separator, and build a {@code String} containing the lines.
|
||||||
|
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
|
||||||
|
* results; however, line comments anywhere else — for example, within
|
||||||
|
* a statement — will be included in the results.
|
||||||
|
* @param resource the {@code EncodedResource} containing the script
|
||||||
|
* to be processed
|
||||||
|
* @param commentPrefix the prefix that identifies comments in the SQL script — typically "--"
|
||||||
|
* @param separator the statement separator in the SQL script — typically ";"
|
||||||
|
* @return a {@code String} containing the script lines
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static String readScript(EncodedResource resource, String commentPrefix,
|
||||||
|
String separator) throws IOException {
|
||||||
|
LineNumberReader lnr = new LineNumberReader(resource.getReader());
|
||||||
|
try {
|
||||||
|
return readScript(lnr, commentPrefix, separator);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
lnr.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a script from the provided {@code LineNumberReader}, using the supplied
|
||||||
|
* comment prefix and statement separator, and build a {@code String} containing the lines.
|
||||||
|
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
|
||||||
|
* results; however, line comments anywhere else — for example, within
|
||||||
|
* a statement — will be included in the results.
|
||||||
|
* @param lineNumberReader the {@code LineNumberReader} containing the script
|
||||||
|
* to be processed
|
||||||
|
* @param commentPrefix the prefix that identifies comments in the SQL script — typically "--"
|
||||||
|
* @param separator the statement separator in the SQL script — typically ";"
|
||||||
|
* @return a {@code String} containing the script lines
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix,
|
||||||
|
String separator) 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();
|
||||||
|
}
|
||||||
|
maybeAddSeparatorToScript(scriptBuilder, separator);
|
||||||
|
return scriptBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void maybeAddSeparatorToScript(StringBuilder scriptBuilder, String separator) {
|
||||||
|
if (separator == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String trimmed = separator.trim();
|
||||||
|
if (trimmed.length() == separator.length()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 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(separator.substring(trimmed.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the provided SQL script contain the specified delimiter?
|
||||||
|
* @param script the SQL script
|
||||||
|
* @param delim character delimiting each statement - typically a ';' character
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static boolean containsSqlScriptDelimiters(String script, char delim) {
|
||||||
|
return containsSqlScriptDelimiters(script, String.valueOf(delim));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the provided SQL script contain the specified delimiter?
|
||||||
|
* @param script the SQL script
|
||||||
|
* @param delim String delimiting each statement - typically a ';' character
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static boolean containsSqlScriptDelimiters(String script, String delim) {
|
||||||
|
boolean inLiteral = false;
|
||||||
|
char[] content = script.toCharArray();
|
||||||
|
for (int i = 0; i < script.length(); i++) {
|
||||||
|
if (content[i] == '\'') {
|
||||||
|
inLiteral = !inLiteral;
|
||||||
|
}
|
||||||
|
if (!inLiteral && script.startsWith(delim, i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the given SQL script.
|
||||||
|
* <p>The script will normally be loaded by classpath. There should be one statement
|
||||||
|
* per line. Any statement separators will be removed.
|
||||||
|
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
|
||||||
|
* @param executor the {@code ScriptStatementExecutor} with which to perform JDBC operations
|
||||||
|
* @param resource the resource (potentially associated with a specific encoding) to load the SQL script from
|
||||||
|
* @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 commentPrefix the script line comment prefix
|
||||||
|
* if not specified
|
||||||
|
* @param separator the script statement separator, defaults to {@code DEFAUT_STATEMENT_SEPARATOR}
|
||||||
|
* if not specified
|
||||||
|
* @param blockCommentStartDelim the script block comment starting delimiter
|
||||||
|
* @param blockCommentEndDelim the script block comment ending delimiter
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public static void executeSqlScript(ScriptStatementExecutor executor, EncodedResource resource,
|
||||||
|
boolean continueOnError, boolean ignoreFailedDrops, String commentPrefix, String separator,
|
||||||
|
String blockCommentStartDelim, String blockCommentEndDelim) throws DataAccessException {
|
||||||
|
|
||||||
|
if (logger.isInfoEnabled()) {
|
||||||
|
logger.info("Executing SQL script from " + resource);
|
||||||
|
}
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
List<String> statements = new LinkedList<String>();
|
||||||
|
String script;
|
||||||
|
try {
|
||||||
|
script = readScript(resource, commentPrefix, separator);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new CannotReadScriptException(resource, ex);
|
||||||
|
}
|
||||||
|
if (separator == null) {
|
||||||
|
separator = DEFAULT_STATEMENT_SEPARATOR;
|
||||||
|
if (!containsSqlScriptDelimiters(script, separator)) {
|
||||||
|
separator = "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
splitSqlScript(script, separator, commentPrefix, blockCommentStartDelim, blockCommentEndDelim, statements);
|
||||||
|
int lineNumber = 0;
|
||||||
|
for (String statement : statements) {
|
||||||
|
lineNumber++;
|
||||||
|
try {
|
||||||
|
int rowsAffected = executor.executeScriptStatement(statement);
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug(rowsAffected + " returned as updateCount for SQL: " + statement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (DataAccessException ex) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ScriptStatementFailedException(statement, lineNumber, resource, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||||
|
if (logger.isInfoEnabled()) {
|
||||||
|
logger.info("Done executing SQL script from " + resource + " in " + elapsedTime + " ms.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to be implemented by an object so that {@code executeScript()} is able to use
|
||||||
|
* it to execute script statements.
|
||||||
|
* @since 4.0.3
|
||||||
|
*/
|
||||||
|
public interface ScriptStatementExecutor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Execute the given SQL statement and return a count of the number of affected rows.
|
||||||
|
* @return the number of rows affected by the statement
|
||||||
|
* @throws DataAccessException if there is a problem during statement execution
|
||||||
|
*/
|
||||||
|
public int executeScriptStatement(String statement) throws DataAccessException;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,238 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.jdbc.datasource.init;
|
||||||
|
|
||||||
|
import java.io.LineNumberReader;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.support.EncodedResource;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.jdbc.UncategorizedSQLException;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||||
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit test cases for ScriptUtils.
|
||||||
|
*
|
||||||
|
* @author Chris Baldwin
|
||||||
|
*/
|
||||||
|
public class ScriptUtilsTests {
|
||||||
|
|
||||||
|
private final EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||||
|
|
||||||
|
private final EmbeddedDatabase db = builder.build();
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate = new JdbcTemplate(db);
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutDown() {
|
||||||
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
TransactionSynchronizationManager.clear();
|
||||||
|
TransactionSynchronizationManager.unbindResource(db);
|
||||||
|
}
|
||||||
|
db.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void splitSqlScriptDelimitedWithSemicolon() {
|
||||||
|
String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
||||||
|
String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
||||||
|
String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)";
|
||||||
|
String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||||
|
String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||||
|
String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||||
|
char delim = ';';
|
||||||
|
String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim;
|
||||||
|
List<String> statements = new ArrayList<String>();
|
||||||
|
ScriptUtils.splitSqlScript(script, delim, statements);
|
||||||
|
assertEquals("wrong number of statements", 3, statements.size());
|
||||||
|
assertEquals("statement 1 not split correctly", cleanedStatement1, statements.get(0));
|
||||||
|
assertEquals("statement 2 not split correctly", cleanedStatement2, statements.get(1));
|
||||||
|
assertEquals("statement 3 not split correctly", cleanedStatement3, statements.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void splitSqlScriptDelimitedWithNewLine() {
|
||||||
|
String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
||||||
|
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||||
|
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||||
|
char delim = '\n';
|
||||||
|
String script = statement1 + delim + statement2 + delim + statement3 + delim;
|
||||||
|
List<String> statements = new ArrayList<String>();
|
||||||
|
ScriptUtils.splitSqlScript(script, delim, statements);
|
||||||
|
assertEquals("wrong number of statements", 3, statements.size());
|
||||||
|
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
|
||||||
|
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
|
||||||
|
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readAndSplitScriptContainingComments() throws Exception {
|
||||||
|
|
||||||
|
EncodedResource resource = new EncodedResource(new ClassPathResource("test-data-with-comments.sql", getClass()));
|
||||||
|
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
|
||||||
|
|
||||||
|
String script = ScriptUtils.readScript(lineNumberReader);
|
||||||
|
|
||||||
|
char delim = ';';
|
||||||
|
List<String> statements = new ArrayList<String>();
|
||||||
|
ScriptUtils.splitSqlScript(script, delim, statements);
|
||||||
|
|
||||||
|
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
|
||||||
|
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||||
|
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||||
|
// Statement 4 addresses the error described in SPR-9982.
|
||||||
|
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
|
||||||
|
|
||||||
|
assertEquals("wrong number of statements", 4, statements.size());
|
||||||
|
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
|
||||||
|
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
|
||||||
|
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
|
||||||
|
assertEquals("statement 4 not split correctly", statement4, statements.get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://jira.springsource.org/browse/SPR-10330">SPR-10330</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception {
|
||||||
|
|
||||||
|
EncodedResource resource = new EncodedResource(new ClassPathResource(
|
||||||
|
"test-data-with-comments-and-leading-tabs.sql", getClass()));
|
||||||
|
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
|
||||||
|
|
||||||
|
String script = ScriptUtils.readScript(lineNumberReader);
|
||||||
|
|
||||||
|
char delim = ';';
|
||||||
|
List<String> statements = new ArrayList<String>();
|
||||||
|
ScriptUtils.splitSqlScript(script, delim, statements);
|
||||||
|
|
||||||
|
String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')";
|
||||||
|
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)";
|
||||||
|
String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)";
|
||||||
|
|
||||||
|
assertEquals("wrong number of statements", 3, statements.size());
|
||||||
|
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
|
||||||
|
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
|
||||||
|
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://jira.springsource.org/browse/SPR-9531">SPR-9531</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void readAndSplitScriptContainingMuliLineComments() throws Exception {
|
||||||
|
EncodedResource resource = new EncodedResource(new ClassPathResource(
|
||||||
|
"test-data-with-multi-line-comments.sql", getClass()));
|
||||||
|
|
||||||
|
String script = ScriptUtils.readScript(resource);
|
||||||
|
|
||||||
|
char delim = ';';
|
||||||
|
List<String> statements = new ArrayList<String>();
|
||||||
|
ScriptUtils.splitSqlScript(script, delim, statements);
|
||||||
|
|
||||||
|
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller')";
|
||||||
|
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Sam' , 'Brannen' )";
|
||||||
|
|
||||||
|
assertEquals("wrong number of statements", 2, statements.size());
|
||||||
|
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
|
||||||
|
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void containsDelimiters() {
|
||||||
|
assertTrue("test with ';' is wrong", !ScriptUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';'));
|
||||||
|
assertTrue("test with delimiter ; is wrong",
|
||||||
|
ScriptUtils.containsSqlScriptDelimiters("select 1; select 2", ';'));
|
||||||
|
assertTrue("test with '\\n' is wrong",
|
||||||
|
!ScriptUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", '\n'));
|
||||||
|
assertTrue("test with delimiter \\n is wrong",
|
||||||
|
ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2", '\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeSqlScript() throws SQLException {
|
||||||
|
EncodedResource schemaResource = new EncodedResource(new ClassPathResource(
|
||||||
|
"users-schema.sql", getClass()));
|
||||||
|
EncodedResource commentResource = new EncodedResource(new ClassPathResource(
|
||||||
|
"test-data-with-multi-line-comments.sql", getClass()));
|
||||||
|
Connection connection = db.getConnection();
|
||||||
|
Statement stmt = connection.createStatement();
|
||||||
|
TestScriptStatementExecutor executor = new TestScriptStatementExecutor(stmt);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ScriptUtils.executeSqlScript(executor, schemaResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
|
||||||
|
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
|
||||||
|
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
|
||||||
|
ScriptUtils.executeSqlScript(executor, commentResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
|
||||||
|
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
|
||||||
|
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stmt.close();
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertUsersDatabaseCreated("Hoeller", "Brannen");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertUsersDatabaseCreated(String... lastNames) {
|
||||||
|
for (String lastName : lastNames) {
|
||||||
|
assertThat("Did not find user with last name [" + lastName + "].",
|
||||||
|
jdbcTemplate.queryForObject("select count(0) from users where last_name = ?", Integer.class, lastName),
|
||||||
|
equalTo(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestScriptStatementExecutor implements ScriptUtils.ScriptStatementExecutor
|
||||||
|
{
|
||||||
|
Statement stmt;
|
||||||
|
|
||||||
|
public TestScriptStatementExecutor(Statement stmt) {
|
||||||
|
super();
|
||||||
|
this.stmt = stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see org.springframework.jdbc.datasource.init.ScriptUtils.ScriptStatementExecutor#executeScriptStatement(java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeScriptStatement(String statement) throws DataAccessException {
|
||||||
|
try {
|
||||||
|
stmt.execute(statement);
|
||||||
|
return stmt.getUpdateCount();
|
||||||
|
}
|
||||||
|
catch (SQLException e) {
|
||||||
|
throw new UncategorizedSQLException(getClass().getName(), statement, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* This is a multi line comment
|
||||||
|
* The next comment line has no text
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
/*
|
||||||
|
* Let's add another comment
|
||||||
|
* that covers multiple lines
|
||||||
|
*/INSERT INTO
|
||||||
|
users(first_name, last_name)
|
||||||
|
VALUES( 'Sam' -- first_name
|
||||||
|
, 'Brannen' -- last_name
|
||||||
|
);--
|
||||||
|
|
@ -182,6 +182,7 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst
|
||||||
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
|
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
|
||||||
* @see #setSqlScriptEncoding
|
* @see #setSqlScriptEncoding
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
||||||
Resource resource = this.applicationContext.getResource(sqlResourcePath);
|
Resource resource = this.applicationContext.getResource(sqlResourcePath);
|
||||||
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
|
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 JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
|
||||||
* @see #setSqlScriptEncoding
|
* @see #setSqlScriptEncoding
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
||||||
Resource resource = this.applicationContext.getResource(sqlResourcePath);
|
Resource resource = this.applicationContext.getResource(sqlResourcePath);
|
||||||
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.IOException;
|
||||||
import java.io.LineNumberReader;
|
import java.io.LineNumberReader;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
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.ResourceLoader;
|
||||||
import org.springframework.core.io.support.EncodedResource;
|
import org.springframework.core.io.support.EncodedResource;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.dao.DataAccessResourceFailureException;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.SqlParameterValue;
|
import org.springframework.jdbc.core.SqlParameterValue;
|
||||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
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;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,17 +41,13 @@ import org.springframework.util.StringUtils;
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Chris Baldwin
|
||||||
* @since 2.5.4
|
* @since 2.5.4
|
||||||
*/
|
*/
|
||||||
public class JdbcTestUtils {
|
public class JdbcTestUtils {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(JdbcTestUtils.class);
|
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.
|
* Count the rows in the given table.
|
||||||
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
|
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
|
||||||
|
|
@ -163,7 +159,10 @@ public class JdbcTestUtils {
|
||||||
* and {@code continueOnError} is {@code false}
|
* and {@code continueOnError} is {@code false}
|
||||||
* @see ResourceDatabasePopulator
|
* @see ResourceDatabasePopulator
|
||||||
* @see #executeSqlScript(JdbcTemplate, Resource, boolean)
|
* @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,
|
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
|
||||||
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
||||||
Resource resource = resourceLoader.getResource(sqlResourcePath);
|
Resource resource = resourceLoader.getResource(sqlResourcePath);
|
||||||
|
|
@ -186,7 +185,10 @@ public class JdbcTestUtils {
|
||||||
* and {@code continueOnError} is {@code false}
|
* and {@code continueOnError} is {@code false}
|
||||||
* @see ResourceDatabasePopulator
|
* @see ResourceDatabasePopulator
|
||||||
* @see #executeSqlScript(JdbcTemplate, EncodedResource, boolean)
|
* @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)
|
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
|
||||||
throws DataAccessException {
|
throws DataAccessException {
|
||||||
executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
|
executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
|
||||||
|
|
@ -205,62 +207,33 @@ public class JdbcTestUtils {
|
||||||
* @throws DataAccessException if there is an error executing a statement
|
* @throws DataAccessException if there is an error executing a statement
|
||||||
* and {@code continueOnError} is {@code false}
|
* and {@code continueOnError} is {@code false}
|
||||||
* @see ResourceDatabasePopulator
|
* @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)
|
public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
|
||||||
throws DataAccessException {
|
throws DataAccessException {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
if (logger.isInfoEnabled()) {
|
private static class JdbcTemplateScriptStatementExecutor implements ScriptStatementExecutor {
|
||||||
logger.info("Executing SQL script from " + resource);
|
|
||||||
}
|
private JdbcTemplate jdbcTemplate;
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
List<String> statements = new LinkedList<String>();
|
public JdbcTemplateScriptStatementExecutor(JdbcTemplate jdbcTemplate) {
|
||||||
LineNumberReader reader = null;
|
super();
|
||||||
try {
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,9 +245,12 @@ public class JdbcTestUtils {
|
||||||
* to be processed
|
* to be processed
|
||||||
* @return a {@code String} containing the script lines
|
* @return a {@code String} containing the script lines
|
||||||
* @see #readScript(LineNumberReader, String)
|
* @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 {
|
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
|
||||||
return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX);
|
return ScriptUtils.readScript(lineNumberReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -287,21 +263,12 @@ public class JdbcTestUtils {
|
||||||
* to be processed
|
* to be processed
|
||||||
* @param commentPrefix the prefix that identifies comments in the SQL script — typically "--"
|
* @param commentPrefix the prefix that identifies comments in the SQL script — typically "--"
|
||||||
* @return a {@code String} containing the script lines
|
* @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 {
|
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix) throws IOException {
|
||||||
String currentStatement = lineNumberReader.readLine();
|
return ScriptUtils.readScript(lineNumberReader, commentPrefix, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -309,19 +276,12 @@ public class JdbcTestUtils {
|
||||||
* @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
|
||||||
* @return {@code true} if the script contains the delimiter; {@code false} otherwise
|
* @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) {
|
public static boolean containsSqlScriptDelimiters(String script, char delim) {
|
||||||
boolean inLiteral = false;
|
return ScriptUtils.containsSqlScriptDelimiters(script, delim);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -335,83 +295,11 @@ public class JdbcTestUtils {
|
||||||
* @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 statements the list that will contain the individual statements
|
* @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) {
|
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -17,21 +17,13 @@
|
||||||
package org.springframework.test.jdbc;
|
package org.springframework.test.jdbc;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
|
||||||
import java.io.LineNumberReader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.core.io.support.EncodedResource;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,6 +32,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Chris Baldwin
|
||||||
* @since 2.5.4
|
* @since 2.5.4
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
|
@ -48,103 +41,6 @@ public class JdbcTestUtilsTests {
|
||||||
@Mock
|
@Mock
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void containsDelimiters() {
|
|
||||||
assertTrue("test with ';' is wrong", !JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';'));
|
|
||||||
assertTrue("test with delimiter ; is wrong",
|
|
||||||
JdbcTestUtils.containsSqlScriptDelimiters("select 1; select 2", ';'));
|
|
||||||
assertTrue("test with '\\n' is wrong",
|
|
||||||
!JdbcTestUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", '\n'));
|
|
||||||
assertTrue("test with delimiter \\n is wrong",
|
|
||||||
JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select 2", '\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void splitSqlScriptDelimitedWithSemicolon() {
|
|
||||||
String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
|
||||||
String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
|
||||||
String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)";
|
|
||||||
String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
|
||||||
String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
|
||||||
String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
|
||||||
char delim = ';';
|
|
||||||
String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim;
|
|
||||||
List<String> statements = new ArrayList<String>();
|
|
||||||
JdbcTestUtils.splitSqlScript(script, delim, statements);
|
|
||||||
assertEquals("wrong number of statements", 3, statements.size());
|
|
||||||
assertEquals("statement 1 not split correctly", cleanedStatement1, statements.get(0));
|
|
||||||
assertEquals("statement 2 not split correctly", cleanedStatement2, statements.get(1));
|
|
||||||
assertEquals("statement 3 not split correctly", cleanedStatement3, statements.get(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void splitSqlScriptDelimitedWithNewLine() {
|
|
||||||
String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
|
||||||
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
|
||||||
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
|
||||||
char delim = '\n';
|
|
||||||
String script = statement1 + delim + statement2 + delim + statement3 + delim;
|
|
||||||
List<String> statements = new ArrayList<String>();
|
|
||||||
JdbcTestUtils.splitSqlScript(script, delim, statements);
|
|
||||||
assertEquals("wrong number of statements", 3, statements.size());
|
|
||||||
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
|
|
||||||
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
|
|
||||||
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readAndSplitScriptContainingComments() throws Exception {
|
|
||||||
|
|
||||||
EncodedResource resource = new EncodedResource(new ClassPathResource("test-data-with-comments.sql", getClass()));
|
|
||||||
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
|
|
||||||
|
|
||||||
String script = JdbcTestUtils.readScript(lineNumberReader);
|
|
||||||
|
|
||||||
char delim = ';';
|
|
||||||
List<String> statements = new ArrayList<String>();
|
|
||||||
JdbcTestUtils.splitSqlScript(script, delim, statements);
|
|
||||||
|
|
||||||
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
|
|
||||||
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
|
||||||
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
|
||||||
// Statement 4 addresses the error described in SPR-9982.
|
|
||||||
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
|
|
||||||
|
|
||||||
assertEquals("wrong number of statements", 4, statements.size());
|
|
||||||
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
|
|
||||||
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
|
|
||||||
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
|
|
||||||
assertEquals("statement 4 not split correctly", statement4, statements.get(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See <a href="https://jira.spring.io/browse/SPR-10330">SPR-10330</a>
|
|
||||||
* @since 4.0
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception {
|
|
||||||
|
|
||||||
EncodedResource resource = new EncodedResource(new ClassPathResource(
|
|
||||||
"test-data-with-comments-and-leading-tabs.sql", getClass()));
|
|
||||||
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
|
|
||||||
|
|
||||||
String script = JdbcTestUtils.readScript(lineNumberReader);
|
|
||||||
|
|
||||||
char delim = ';';
|
|
||||||
List<String> statements = new ArrayList<String>();
|
|
||||||
JdbcTestUtils.splitSqlScript(script, delim, statements);
|
|
||||||
|
|
||||||
String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')";
|
|
||||||
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)";
|
|
||||||
String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)";
|
|
||||||
|
|
||||||
assertEquals("wrong number of statements", 3, statements.size());
|
|
||||||
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
|
|
||||||
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
|
|
||||||
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deleteWithoutWhereClause() throws Exception {
|
public void deleteWithoutWhereClause() throws Exception {
|
||||||
given(jdbcTemplate.update("DELETE FROM person")).willReturn(10);
|
given(jdbcTemplate.update("DELETE FROM person")).willReturn(10);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue