Support comments in SQL scripts in JdbcTestUtils
Prior to this commit, utility methods in JdbcTestUtils interpreted SQL comments as separate statements, resulting in an exception when such a script is executed. This commit addresses this issue by introducing a readScript(lineNumberReader, String) method that accepts a comment prefix. Comment lines are therefore no longer returned in the parsed script. Furthermore, the existing readScript(lineNumberReader) method now delegates to this new readScript() method, supplying "--" as the default comment prefix. Issue: SPR-9593
This commit is contained in:
parent
7c538bd588
commit
4aaf014cc6
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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,17 +19,18 @@ package org.springframework.jdbc.datasource.init;
|
|||
import org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
/**
|
||||
* Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts could
|
||||
* not be read during population.
|
||||
* Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts cannot
|
||||
* be read during population.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class CannotReadScriptException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructor a new CannotReadScriptException.
|
||||
* @param resource the resource that could not be read from
|
||||
* Construct a new {@code CannotReadScriptException}.
|
||||
* @param resource the resource that cannot be read from
|
||||
* @param cause the underlying cause of the resource access failure
|
||||
*/
|
||||
public CannotReadScriptException(EncodedResource resource, Throwable cause) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
@ -23,16 +23,18 @@ import org.springframework.core.io.support.EncodedResource;
|
|||
* 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 {
|
||||
|
||||
/**
|
||||
* Constructor a new ScriptStatementFailedException.
|
||||
* Construct a new {@code ScriptStatementFailedException}.
|
||||
* @param statement the actual SQL statement that failed
|
||||
* @param lineNumber the line number in the SQL script
|
||||
* @param resource the resource that could not be read from
|
||||
* @param cause the underlying cause of the resource access failure
|
||||
* @param resource the resource from which the SQL statement was read
|
||||
* @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 +
|
||||
|
|
|
@ -30,6 +30,7 @@ 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.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -47,6 +48,10 @@ 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.
|
||||
|
@ -124,10 +129,10 @@ public class JdbcTestUtils {
|
|||
* exception in the event of an error
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and {@code continueOnError} is {@code false}
|
||||
* @see ResourceDatabasePopulator
|
||||
*/
|
||||
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
|
||||
String sqlResourcePath, boolean continueOnError) throws DataAccessException {
|
||||
|
||||
Resource resource = resourceLoader.getResource(sqlResourcePath);
|
||||
executeSqlScript(jdbcTemplate, resource, continueOnError);
|
||||
}
|
||||
|
@ -145,10 +150,10 @@ public class JdbcTestUtils {
|
|||
* exception in the event of an error
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and {@code continueOnError} is {@code false}
|
||||
* @see ResourceDatabasePopulator
|
||||
*/
|
||||
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
|
||||
throws DataAccessException {
|
||||
|
||||
executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
|
||||
}
|
||||
|
||||
|
@ -164,6 +169,7 @@ public class JdbcTestUtils {
|
|||
* exception in the event of an error
|
||||
* @throws DataAccessException if there is an error executing a statement
|
||||
* and {@code continueOnError} is {@code false}
|
||||
* @see ResourceDatabasePopulator
|
||||
*/
|
||||
public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
|
||||
throws DataAccessException {
|
||||
|
@ -177,12 +183,14 @@ public class JdbcTestUtils {
|
|||
try {
|
||||
reader = new LineNumberReader(resource.getReader());
|
||||
String script = readScript(reader);
|
||||
char delimiter = ';';
|
||||
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()) {
|
||||
|
@ -192,7 +200,8 @@ public class JdbcTestUtils {
|
|||
catch (DataAccessException ex) {
|
||||
if (continueOnError) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("SQL statement [" + statement + "] failed", ex);
|
||||
logger.warn("Failed to execute SQL script statement at line " + lineNumber
|
||||
+ " of resource " + resource + ": " + statement, ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -213,24 +222,40 @@ public class JdbcTestUtils {
|
|||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a script from the provided {@code LineNumberReader} and build a
|
||||
* {@code String} containing the lines.
|
||||
* 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)
|
||||
*/
|
||||
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
|
||||
return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a script from the provided {@code LineNumberReader}, using the supplied
|
||||
* comment prefix, and build a {@code String} containing the lines.
|
||||
* @param lineNumberReader the {@code LineNumberReader} containing the script
|
||||
* to be processed
|
||||
* @param commentPrefix the line prefix that identifies comments in the SQL script
|
||||
* @return a {@code String} containing the script lines
|
||||
*/
|
||||
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)) {
|
||||
if (StringUtils.hasText(currentStatement)
|
||||
&& (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) {
|
||||
if (scriptBuilder.length() > 0) {
|
||||
scriptBuilder.append('\n');
|
||||
}
|
||||
|
@ -270,26 +295,71 @@ public class JdbcTestUtils {
|
|||
* @param statements the list that will contain the individual statements
|
||||
*/
|
||||
public static void splitSqlScript(String script, char delim, List<String> statements) {
|
||||
splitSqlScript(script, "" + delim, statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split an SQL script into separate statements delimited with the provided delimiter
|
||||
* character. Each individual statement will be added to the provided {@code List}.
|
||||
* @param script the SQL script
|
||||
* @param delim character delimiting each statement — typically a ';' character
|
||||
* @param statements the List that will contain the individual statements
|
||||
*/
|
||||
private static void splitSqlScript(String script, String delim, 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++) {
|
||||
if (content[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 (content[i] == delim && !inLiteral) {
|
||||
if (sb.length() > 0) {
|
||||
statements.add(sb.toString());
|
||||
sb = new StringBuilder();
|
||||
if (!inLiteral) {
|
||||
if (startsWithDelimiter(script, i, delim)) {
|
||||
if (sb.length() > 0) {
|
||||
statements.add(sb.toString());
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
i += delim.length() - 1;
|
||||
continue;
|
||||
}
|
||||
else if (c == '\n' || c == '\t') {
|
||||
c = ' ';
|
||||
}
|
||||
}
|
||||
else {
|
||||
sb.append(content[i]);
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
if (StringUtils.hasText(sb)) {
|
||||
statements.add(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the substring of a given source {@link String} starting at the
|
||||
* given index starts with the given delimiter.
|
||||
* @param source the source {@link String} to inspect
|
||||
* @param startIndex the index to look for the delimiter
|
||||
* @param delim the delimiter to look for
|
||||
*/
|
||||
private static boolean startsWithDelimiter(String source, int startIndex, String delim) {
|
||||
int endIndex = startIndex + delim.length();
|
||||
if (source.length() < endIndex) {
|
||||
// String is too short to contain the delimiter
|
||||
return false;
|
||||
}
|
||||
return source.substring(startIndex, endIndex).equals(delim);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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,18 +16,21 @@
|
|||
|
||||
package org.springframework.test.jdbc;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.LineNumberReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link JdbcTestUtils}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Sam Brannen
|
||||
* @since 2.5.4
|
||||
*/
|
||||
public class JdbcTestUtilsTests {
|
||||
|
@ -45,12 +48,29 @@ public class JdbcTestUtilsTests {
|
|||
|
||||
@Test
|
||||
public void splitSqlScriptDelimitedWithSemicolon() {
|
||||
String statement1 = "insert into customer (id, name) \n"
|
||||
+ "values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
||||
String statement2 = "insert into orders(id, order_date, customer_id) \n" + "values (1, '2008-01-02', 2)";
|
||||
String statement3 = "insert into orders(id, order_date, customer_id) " + "values (1, '2008-01-02', 2)";
|
||||
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 = statement1 + delim + statement2 + delim + statement3;
|
||||
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());
|
||||
|
@ -60,14 +80,22 @@ public class JdbcTestUtilsTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void splitSqlScriptDelimitedWithNewLine() {
|
||||
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)";
|
||||
char delim = '\n';
|
||||
String script = statement1 + delim + statement2 + delim + statement3;
|
||||
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);
|
||||
assertFalse("script should not contain --", script.contains("--"));
|
||||
|
||||
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)";
|
||||
|
||||
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));
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
insert into customer (id, name)
|
||||
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
|
||||
-- This is also a comment.
|
||||
insert into orders(id, order_date, customer_id)
|
||||
values (1, '2008-01-02', 2);
|
||||
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
|
Loading…
Reference in New Issue