diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java index 4f7414cbbd9..93388defff4 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java @@ -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) { diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java index 906b788e047..016ab1eed9a 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java @@ -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 + diff --git a/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java b/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java index cb9ec9f0cbc..0323bff0928 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java +++ b/org.springframework.test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java @@ -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 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 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); + } + } diff --git a/org.springframework.test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java b/org.springframework.test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java index 4817c7127b1..f688be143ad 100644 --- a/org.springframework.test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java +++ b/org.springframework.test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java @@ -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 statements = new ArrayList(); + 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 statements = new ArrayList(); 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 statements = new ArrayList(); 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)); diff --git a/org.springframework.test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql b/org.springframework.test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql new file mode 100644 index 00000000000..50dfb419a05 --- /dev/null +++ b/org.springframework.test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql @@ -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); \ No newline at end of file