Merge pull request #485 from cbaldwin74/SPR-9531
* SPR-9531: Refactor SQL script support Support multi-line comments in SQL scripts
This commit is contained in:
@@ -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.
|
||||
@@ -31,7 +31,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* with a specific encoding to be used for reading from the resource.
|
||||
*
|
||||
* <p>Used as argument for operations that support to read content with
|
||||
* a specific encoding (usually through a {@code java.io.Reader}.
|
||||
* a specific encoding (usually through a {@code java.io.Reader}).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.2.6
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -19,14 +19,14 @@ package org.springframework.jdbc.datasource.init;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
/**
|
||||
* Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts cannot
|
||||
* be read during population.
|
||||
* Thrown by {@link ScriptUtils} if an SQL script cannot be read.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class CannotReadScriptException extends RuntimeException {
|
||||
public class CannotReadScriptException extends ScriptException {
|
||||
|
||||
/**
|
||||
* Construct a new {@code CannotReadScriptException}.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -20,25 +20,28 @@ import java.sql.Connection;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility methods for executing a DatabasePopulator.
|
||||
* Utility methods for executing a {@link DatabasePopulator}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Oliver Gierke
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class DatabasePopulatorUtils {
|
||||
|
||||
/**
|
||||
* Execute the given DatabasePopulator against the given DataSource.
|
||||
* @param populator the DatabasePopulator to execute
|
||||
* @param dataSource the DataSource to execute against
|
||||
* Execute the given {@link DatabasePopulator} against the given {@link DataSource}.
|
||||
* @param populator the {@code DatabasePopulator} to execute
|
||||
* @param dataSource the {@code DataSource} to execute against
|
||||
* @throws DataAccessException if an error occurs
|
||||
*/
|
||||
public static void execute(DatabasePopulator populator, DataSource dataSource) {
|
||||
public static void execute(DatabasePopulator populator, DataSource dataSource) throws DataAccessException {
|
||||
Assert.notNull(populator, "DatabasePopulator must be provided");
|
||||
Assert.notNull(dataSource, "DataSource must be provided");
|
||||
try {
|
||||
|
||||
@@ -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.
|
||||
@@ -16,22 +16,14 @@
|
||||
|
||||
package org.springframework.jdbc.datasource.init;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.LineNumberReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.Resource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Populates a database from SQL scripts defined in external resources.
|
||||
@@ -45,24 +37,22 @@ import org.springframework.util.StringUtils;
|
||||
* @author Chris Beams
|
||||
* @author Oliver Gierke
|
||||
* @author Sam Brannen
|
||||
* @author Chris Baldwin
|
||||
* @since 3.0
|
||||
*/
|
||||
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 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;
|
||||
|
||||
@@ -71,7 +61,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
|
||||
/**
|
||||
* Add a script to execute to populate the database.
|
||||
* @param script the path to a SQL script
|
||||
* @param script the path to an SQL script
|
||||
*/
|
||||
public void addScript(Resource script) {
|
||||
this.scripts.add(script);
|
||||
@@ -87,8 +77,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
|
||||
/**
|
||||
* Specify the encoding for SQL scripts, if different from the platform encoding.
|
||||
* Note setting this property has no effect on added scripts that are already
|
||||
* {@link EncodedResource encoded resources}.
|
||||
* <p>Note that setting this property has no effect on added scripts that are
|
||||
* already {@linkplain EncodedResource encoded resources}.
|
||||
* @see #addScript(Resource)
|
||||
*/
|
||||
public void setSqlScriptEncoding(String sqlScriptEncoding) {
|
||||
@@ -96,23 +86,46 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the statement separator, if a custom one. Default is ";".
|
||||
* Specify the statement separator, if a custom one.
|
||||
* <p>Default is ";".
|
||||
*/
|
||||
public void setSeparator(String separator) {
|
||||
this.separator = separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the line prefix that identifies comments in the SQL script.
|
||||
* Default is "--".
|
||||
* Set the prefix that identifies line comments within the SQL scripts.
|
||||
* <p>Default is "--".
|
||||
*/
|
||||
public void setCommentPrefix(String commentPrefix) {
|
||||
this.commentPrefix = commentPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start delimiter that identifies block comments within the SQL
|
||||
* scripts.
|
||||
* <p>Default is "/*".
|
||||
* @since 4.0.3
|
||||
* @see #setBlockCommentEndDelimiter
|
||||
*/
|
||||
public void setBlockCommentStartDelimiter(String blockCommentStartDelimiter) {
|
||||
this.blockCommentStartDelimiter = blockCommentStartDelimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the end delimiter that identifies block comments within the SQL
|
||||
* scripts.
|
||||
* <p>Default is "*/".
|
||||
* @since 4.0.3
|
||||
* @see #setBlockCommentStartDelimiter
|
||||
*/
|
||||
public void setBlockCommentEndDelimiter(String blockCommentEndDelimiter) {
|
||||
this.blockCommentEndDelimiter = blockCommentEndDelimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to indicate that all failures in SQL should be logged but not cause a failure.
|
||||
* Defaults to false.
|
||||
* <p>Defaults to {@code false}.
|
||||
*/
|
||||
public void setContinueOnError(boolean continueOnError) {
|
||||
this.continueOnError = continueOnError;
|
||||
@@ -121,18 +134,20 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
/**
|
||||
* Flag to indicate that a failed SQL {@code DROP} statement can be ignored.
|
||||
* <p>This is useful for non-embedded databases whose SQL dialect does not support an
|
||||
* {@code IF EXISTS} clause in a {@code DROP}. The default is false so that if the
|
||||
* populator runs accidentally, it will fail fast when the script starts with a {@code DROP}.
|
||||
* {@code IF EXISTS} clause in a {@code DROP} statement.
|
||||
* <p>The default is {@code false} so that if the populator runs accidentally, it will
|
||||
* fail fast if the script starts with a {@code DROP} statement.
|
||||
*/
|
||||
public void setIgnoreFailedDrops(boolean ignoreFailedDrops) {
|
||||
this.ignoreFailedDrops = ignoreFailedDrops;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void populate(Connection connection) throws SQLException {
|
||||
for (Resource script : this.scripts) {
|
||||
executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops);
|
||||
ScriptUtils.executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError,
|
||||
this.ignoreFailedDrops, this.commentPrefix, this.separator, this.blockCommentStartDelimiter,
|
||||
this.blockCommentEndDelimiter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,214 +159,4 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
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,45 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Root of the hierarchy of SQL script exceptions.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class ScriptException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructor for {@code ScriptException}.
|
||||
* @param message the detail message
|
||||
*/
|
||||
public ScriptException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for {@code ScriptException}.
|
||||
* @param message the detail message
|
||||
* @param cause the root cause
|
||||
*/
|
||||
public ScriptException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
/**
|
||||
* Thrown by {@link ScriptUtils} if an SQL script cannot be properly parsed.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ScriptParseException extends ScriptException {
|
||||
|
||||
/**
|
||||
* Construct a new {@code ScriptParseException}.
|
||||
* @param message detailed message
|
||||
* @param resource the resource from which the SQL script was read
|
||||
*/
|
||||
public ScriptParseException(String message, EncodedResource resource) {
|
||||
super(buildMessage(message, resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@code ScriptParseException}.
|
||||
* @param message detailed message
|
||||
* @param resource the resource from which the SQL script was read
|
||||
* @param cause the underlying cause of the failure
|
||||
*/
|
||||
public ScriptParseException(String message, EncodedResource resource, Throwable cause) {
|
||||
super(buildMessage(message, resource), cause);
|
||||
}
|
||||
|
||||
private static String buildMessage(String message, EncodedResource resource) {
|
||||
return String.format("Failed to parse SQL script from resource [%s]: %s", (resource == null ? "<unknown>"
|
||||
: resource), message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.
|
||||
@@ -19,15 +19,15 @@ package org.springframework.jdbc.datasource.init;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
/**
|
||||
* Thrown by {@link ResourceDatabasePopulator} if a statement in one of its SQL scripts
|
||||
* failed when executing it against the target database.
|
||||
* Thrown by {@link ScriptUtils} if a statement in an SQL script failed when
|
||||
* executing it against the target database.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0.5
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ScriptStatementFailedException extends RuntimeException {
|
||||
public class ScriptStatementFailedException extends ScriptException {
|
||||
|
||||
/**
|
||||
* Construct a new {@code ScriptStatementFailedException}.
|
||||
@@ -37,8 +37,8 @@ public class ScriptStatementFailedException extends RuntimeException {
|
||||
* @param cause the underlying cause of the failure
|
||||
*/
|
||||
public ScriptStatementFailedException(String statement, int lineNumber, EncodedResource resource, Throwable cause) {
|
||||
super("Failed to execute SQL script statement at line " + lineNumber +
|
||||
" of resource " + resource + ": " + statement, cause);
|
||||
super("Failed to execute SQL script statement at line " + lineNumber + " of resource " + resource + ": "
|
||||
+ statement, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* 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.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
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.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Generic utility methods for working with SQL scripts. Mainly for internal use
|
||||
* within the framework.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @author Keith Donald
|
||||
* @author Dave Syer
|
||||
* @author Chris Beams
|
||||
* @author Oliver Gierke
|
||||
* @author Chris Baldwin
|
||||
* @since 4.0.3
|
||||
*/
|
||||
public abstract class ScriptUtils {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
|
||||
|
||||
/**
|
||||
* Default statement separator within SQL scripts.
|
||||
*/
|
||||
public static final String DEFAULT_STATEMENT_SEPARATOR = ";";
|
||||
|
||||
/**
|
||||
* Default prefix for line comments within SQL scripts.
|
||||
*/
|
||||
public static final String DEFAULT_COMMENT_PREFIX = "--";
|
||||
|
||||
/**
|
||||
* Default start delimiter for block comments within SQL scripts.
|
||||
*/
|
||||
public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = "/*";
|
||||
|
||||
/**
|
||||
* Default end delimiter for block comments within SQL scripts.
|
||||
*/
|
||||
public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/";
|
||||
|
||||
|
||||
/**
|
||||
* Prevent instantiation of this utility class.
|
||||
*/
|
||||
private ScriptUtils() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 the script, {@value #DEFAULT_COMMENT_PREFIX} 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 output. Similarly,
|
||||
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
|
||||
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
|
||||
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
|
||||
* in a block comment will be omitted from the output. In addition, multiple
|
||||
* adjacent whitespace characters will be collapsed into a single space.
|
||||
* @param script the SQL script
|
||||
* @param delimiter character delimiting each statement — typically a ';' character
|
||||
* @param statements the list that will contain the individual statements
|
||||
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
|
||||
*/
|
||||
public static void splitSqlScript(String script, char delimiter, List<String> statements) throws ScriptException {
|
||||
splitSqlScript(null, script, String.valueOf(delimiter), 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 the script, 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 output. Similarly, the provided
|
||||
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
|
||||
* delimiters will be honored: any text enclosed in a block comment will be
|
||||
* omitted from the output. In addition, multiple adjacent whitespace characters
|
||||
* will be collapsed into a single space.
|
||||
* @param resource the resource from which the script was read
|
||||
* @param script the SQL script; never {@code null} or empty
|
||||
* @param delimiter text delimiting each statement — typically a ';'
|
||||
* character; never {@code null}
|
||||
* @param commentPrefix the prefix that identifies SQL line comments —
|
||||
* typically "--"; never {@code null} or empty
|
||||
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
|
||||
* never {@code null} or empty
|
||||
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
|
||||
* never {@code null} or empty
|
||||
* @param statements the list that will contain the individual statements
|
||||
*/
|
||||
public static void splitSqlScript(EncodedResource resource, String script, String delimiter, String commentPrefix,
|
||||
String blockCommentStartDelimiter, String blockCommentEndDelimiter, List<String> statements)
|
||||
throws ScriptException {
|
||||
|
||||
Assert.hasText(script, "script must not be null or empty");
|
||||
Assert.notNull(delimiter, "delimiter must not be null");
|
||||
Assert.hasText(commentPrefix, "commentPrefix must not be null or empty");
|
||||
Assert.hasText(blockCommentStartDelimiter, "blockCommentStartDelimiter must not be null or empty");
|
||||
Assert.hasText(blockCommentEndDelimiter, "blockCommentEndDelimiter must not be null or empty");
|
||||
|
||||
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(delimiter, i)) {
|
||||
// we've reached the end of the current statement
|
||||
if (sb.length() > 0) {
|
||||
statements.add(sb.toString());
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
i += delimiter.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(blockCommentStartDelimiter, i)) {
|
||||
// skip over any block comments
|
||||
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
|
||||
if (indexOfCommentEnd > i) {
|
||||
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
throw new ScriptParseException(String.format("Missing block comment end delimiter [%s].",
|
||||
blockCommentEndDelimiter), resource);
|
||||
}
|
||||
}
|
||||
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 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
|
||||
*/
|
||||
static String readScript(EncodedResource resource) throws IOException {
|
||||
return readScript(resource, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private 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
|
||||
*/
|
||||
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 String delimiting each statement - typically a ';' character
|
||||
*/
|
||||
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>Statement separators and comments will be removed before executing
|
||||
* individual statements within the supplied script.
|
||||
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
|
||||
* @param connection the JDBC connection to use to execute the script; already
|
||||
* configured and ready to use
|
||||
* @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 or not to continue in the event of specifically
|
||||
* an error on a {@code DROP} statement
|
||||
* @param commentPrefix the prefix that identifies comments in the SQL script —
|
||||
* typically "--"
|
||||
* @param separator the script statement separator; defaults to
|
||||
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified
|
||||
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter; never
|
||||
* {@code null} or empty
|
||||
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter; never
|
||||
* {@code null} or empty
|
||||
*/
|
||||
public static void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
|
||||
boolean ignoreFailedDrops, String commentPrefix, String separator, String blockCommentStartDelimiter,
|
||||
String blockCommentEndDelimiter) throws SQLException, ScriptException {
|
||||
|
||||
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(resource, script, separator, commentPrefix, blockCommentStartDelimiter,
|
||||
blockCommentEndDelimiter, 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("Executed SQL script from " + resource + " in " + elapsedTime + " ms.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -40,9 +40,7 @@ import static org.mockito.BDDMockito.*;
|
||||
*/
|
||||
public class DatabasePopulatorTests {
|
||||
|
||||
private final EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||
|
||||
private final EmbeddedDatabase db = builder.build();
|
||||
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build();
|
||||
|
||||
private final ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
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.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.*;
|
||||
|
||||
/**
|
||||
* Unit and integration tests for {@link ScriptUtils}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Sam Brannen
|
||||
* @author Phillip Webb
|
||||
* @author Chris Baldwin
|
||||
*/
|
||||
public class ScriptUtilsTests {
|
||||
|
||||
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build();
|
||||
|
||||
|
||||
@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()));
|
||||
|
||||
String script = ScriptUtils.readScript(resource);
|
||||
|
||||
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()));
|
||||
|
||||
String script = ScriptUtils.readScript(resource);
|
||||
|
||||
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();
|
||||
|
||||
ScriptUtils.executeSqlScript(connection, 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(connection, commentResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
|
||||
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
|
||||
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
|
||||
|
||||
assertUsersDatabaseCreated("Hoeller", "Brannen");
|
||||
}
|
||||
|
||||
private void assertUsersDatabaseCreated(String... lastNames) {
|
||||
final JdbcTemplate jdbcTemplate = new JdbcTemplate(db);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
);--
|
||||
@@ -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.
|
||||
@@ -21,9 +21,10 @@ import javax.sql.DataSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
@@ -171,21 +172,24 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst
|
||||
/**
|
||||
* Execute the given SQL script.
|
||||
* <p>Use with caution outside of a transaction!
|
||||
* <p>The script will normally be loaded by classpath. There should be one
|
||||
* statement per line. Any semicolons will be removed. <b>Do not use this
|
||||
* method to execute DDL if you expect rollback.</b>
|
||||
* <p>The script will normally be loaded by classpath.
|
||||
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
|
||||
* @param sqlResourcePath the Spring resource path for the SQL script
|
||||
* @param continueOnError whether or not to continue without throwing an
|
||||
* exception in the event of an error
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and continueOnError was {@code false}
|
||||
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
|
||||
* @see ResourceDatabasePopulator
|
||||
* @see DatabasePopulatorUtils
|
||||
* @see #setSqlScriptEncoding
|
||||
*/
|
||||
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
||||
Resource resource = this.applicationContext.getResource(sqlResourcePath);
|
||||
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
|
||||
continueOnError);
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
databasePopulator.setContinueOnError(continueOnError);
|
||||
databasePopulator.addScript(resource);
|
||||
databasePopulator.setSqlScriptEncoding(this.sqlScriptEncoding);
|
||||
|
||||
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -21,9 +21,10 @@ import javax.sql.DataSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
import org.springframework.test.jdbc.JdbcTestUtils;
|
||||
@@ -162,21 +163,24 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst
|
||||
/**
|
||||
* Execute the given SQL script.
|
||||
* <p>Use with caution outside of a transaction!
|
||||
* <p>The script will normally be loaded by classpath. There should be one
|
||||
* statement per line. Any semicolons will be removed. <b>Do not use this
|
||||
* method to execute DDL if you expect rollback.</b>
|
||||
* <p>The script will normally be loaded by classpath.
|
||||
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
|
||||
* @param sqlResourcePath the Spring resource path for the SQL script
|
||||
* @param continueOnError whether or not to continue without throwing an
|
||||
* exception in the event of an error
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and continueOnError was {@code false}
|
||||
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
|
||||
* @see ResourceDatabasePopulator
|
||||
* @see DatabasePopulatorUtils
|
||||
* @see #setSqlScriptEncoding
|
||||
*/
|
||||
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
||||
Resource resource = this.applicationContext.getResource(sqlResourcePath);
|
||||
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
|
||||
continueOnError);
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
databasePopulator.setContinueOnError(continueOnError);
|
||||
databasePopulator.addScript(resource);
|
||||
databasePopulator.setSqlScriptEncoding(this.sqlScriptEncoding);
|
||||
|
||||
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,19 +18,20 @@ 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;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
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.DatabasePopulatorUtils;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.jdbc.datasource.init.ScriptUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -41,16 +42,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.
|
||||
@@ -121,14 +119,13 @@ public class JdbcTestUtils {
|
||||
* optionally the scale.
|
||||
* @return the number of rows deleted from the table
|
||||
*/
|
||||
public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName,
|
||||
String whereClause, Object... args) {
|
||||
public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName, String whereClause,
|
||||
Object... args) {
|
||||
String sql = "DELETE FROM " + tableName;
|
||||
if (StringUtils.hasText(whereClause)) {
|
||||
sql += " WHERE " + whereClause;
|
||||
}
|
||||
int rowCount = (args != null && args.length > 0 ? jdbcTemplate.update(sql, args)
|
||||
: jdbcTemplate.update(sql));
|
||||
int rowCount = (args != null && args.length > 0 ? jdbcTemplate.update(sql, args) : jdbcTemplate.update(sql));
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Deleted " + rowCount + " rows from table " + tableName);
|
||||
}
|
||||
@@ -162,8 +159,13 @@ public class JdbcTestUtils {
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and {@code continueOnError} is {@code false}
|
||||
* @see ResourceDatabasePopulator
|
||||
* @see DatabasePopulatorUtils
|
||||
* @see #executeSqlScript(JdbcTemplate, Resource, boolean)
|
||||
* @deprecated as of Spring 4.0.3, in favor of using
|
||||
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript}
|
||||
* or {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
|
||||
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
||||
Resource resource = resourceLoader.getResource(sqlResourcePath);
|
||||
@@ -185,8 +187,13 @@ public class JdbcTestUtils {
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and {@code continueOnError} is {@code false}
|
||||
* @see ResourceDatabasePopulator
|
||||
* @see DatabasePopulatorUtils
|
||||
* @see #executeSqlScript(JdbcTemplate, EncodedResource, boolean)
|
||||
* @deprecated as of Spring 4.0.3, in favor of using
|
||||
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript}
|
||||
* or {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
|
||||
throws DataAccessException {
|
||||
executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
|
||||
@@ -205,63 +212,20 @@ public class JdbcTestUtils {
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and {@code continueOnError} is {@code false}
|
||||
* @see ResourceDatabasePopulator
|
||||
* @see DatabasePopulatorUtils
|
||||
* @deprecated as of Spring 4.0.3, in favor of using
|
||||
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript}
|
||||
* or {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
|
||||
throws DataAccessException {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
databasePopulator.setContinueOnError(continueOnError);
|
||||
databasePopulator.addScript(resource.getResource());
|
||||
databasePopulator.setSqlScriptEncoding(resource.getEncoding());
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,9 +236,12 @@ 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, String, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
|
||||
return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX);
|
||||
return readScript(lineNumberReader, ScriptUtils.DEFAULT_COMMENT_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,21 +254,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,19 +267,12 @@ 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}
|
||||
*/
|
||||
@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, String.valueOf(delim));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,83 +286,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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.test.jdbc;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link JdbcTestUtils}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.0.3
|
||||
* @see JdbcTestUtilsTests
|
||||
*/
|
||||
public class JdbcTestUtilsIntegrationTests {
|
||||
|
||||
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build();
|
||||
|
||||
private JdbcTemplate jdbcTemplate = new JdbcTemplate(db);
|
||||
|
||||
|
||||
@After
|
||||
public void shutdown() {
|
||||
db.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void executeSqlScriptsAndcountRowsInTableWhere() throws Exception {
|
||||
|
||||
for (String script : Arrays.asList("schema.sql", "data.sql")) {
|
||||
Resource resource = new ClassPathResource(script, getClass());
|
||||
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource), false);
|
||||
}
|
||||
|
||||
assertEquals(1, JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "person", "name = 'bob'"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -16,31 +16,22 @@
|
||||
|
||||
package org.springframework.test.jdbc;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
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.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
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 static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link JdbcTestUtils}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Sam Brannen
|
||||
* @author Phillip Webb
|
||||
* @since 2.5.4
|
||||
* @see JdbcTestUtilsIntegrationTests
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JdbcTestUtilsTests {
|
||||
@@ -49,102 +40,6 @@ public class JdbcTestUtilsTests {
|
||||
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
|
||||
public void deleteWithoutWhereClause() throws Exception {
|
||||
given(jdbcTemplate.update("DELETE FROM person")).willReturn(10);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO person VALUES('bob');
|
||||
@@ -0,0 +1,4 @@
|
||||
CREATE TABLE person (
|
||||
name VARCHAR(20) NOT NULL,
|
||||
PRIMARY KEY(name)
|
||||
);
|
||||
Reference in New Issue
Block a user