From bb67cd4657ee288d9a0a80c8e81f24308a6b1c0a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 16 Mar 2014 16:41:08 +0100 Subject: [PATCH] Fix regression for newline separators in SQL scripts Changes made in conjunction with SPR-9531, introduced a regression with regard to support for using a single newline character as the statement separator within SQL scripts. Investigation of the cause of this issue resulted in the discovery of another, similar issue: support for multiple newlines as a statement separator has been broken for years but has gone unnoticed until now. The reason that both of these issues have gone unnoticed is a result of the fact that the test suite only executes SQL script integration tests against HSQL DB, and HSQL does not care if two statements occur on the same line; whereas, the H2 database will throw an exception if multiple statements are included on the same line when executing an update. This commit addresses both of these issues and provides further enhancements to Spring's SQL script support as follows. - ScriptUtils now properly checks if the supplied script contains the custom statement separator or default separator before falling back to the 'fallback' separator (i.e., newline). - Introduced FALLBACK_STATEMENT_SEPARATOR constant in ScriptUtils. - ScriptUtils.readScript() no longer omits empty lines from the input file since a statement separator string may in fact be composed of multiple newline characters. - Introduced overloaded variants of splitSqlScript() and executeSqlScript() in ScriptUtils with smaller argument lists for common use cases. - Extracted AbstractDatabasePopulatorTests from DatabasePopulatorTests and introduced concrete HsqlDatabasePopulatorTests and H2DatabasePopulatorTests subclasses for testing against HSQL and H2. - Split ScriptUtilsTests into ScriptUtilsUnitTests and ScriptUtilsIntegrationTests for faster builds. Issue: SPR-11560 --- .../jdbc/datasource/init/ScriptUtils.java | 118 ++++++++++++---- .../AbstractDatabaseInitializationTests.java | 64 +++++++++ ...va => AbstractDatabasePopulatorTests.java} | 76 ++++------ .../init/H2DatabasePopulatorTests.java | 31 +++++ .../init/HsqlDatabasePopulatorTests.java | 31 +++++ .../init/ScriptUtilsIntegrationTests.java | 58 ++++++++ ...lsTests.java => ScriptUtilsUnitTests.java} | 130 +++++++----------- 7 files changed, 359 insertions(+), 149 deletions(-) create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/AbstractDatabaseInitializationTests.java rename spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/{DatabasePopulatorTests.java => AbstractDatabasePopulatorTests.java} (89%) create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/H2DatabasePopulatorTests.java create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/HsqlDatabasePopulatorTests.java create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java rename spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/{ScriptUtilsTests.java => ScriptUtilsUnitTests.java} (61%) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java index f43a42304d..68918f5150 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java @@ -26,6 +26,7 @@ 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.Assert; import org.springframework.util.StringUtils; @@ -53,6 +54,13 @@ public abstract class ScriptUtils { */ public static final String DEFAULT_STATEMENT_SEPARATOR = ";"; + /** + * Fallback statement separator within SQL scripts. + *

Used if neither a custom defined separator nor the + * {@link #DEFAULT_STATEMENT_SEPARATOR} is present in a given script. + */ + public static final String FALLBACK_STATEMENT_SEPARATOR = "\n"; + /** * Default prefix for line comments within SQL scripts. */ @@ -78,7 +86,7 @@ public abstract class ScriptUtils { /** * Split an SQL script into separate statements delimited by the provided - * delimiter character. Each individual statement will be added to the + * separator character. Each individual statement will be added to the * provided {@code List}. *

Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the * comment prefix; any text beginning with the comment prefix and extending to @@ -89,18 +97,41 @@ public abstract class ScriptUtils { * 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 separator character separating each statement — typically a ';' * @param statements the list that will contain the individual statements + * @see #splitSqlScript(String, String, List) * @see #splitSqlScript(EncodedResource, String, String, String, String, String, List) */ - public static void splitSqlScript(String script, char delimiter, List statements) throws ScriptException { - splitSqlScript(null, script, String.valueOf(delimiter), DEFAULT_COMMENT_PREFIX, - DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements); + public static void splitSqlScript(String script, char separator, List statements) throws ScriptException { + splitSqlScript(script, String.valueOf(separator), statements); } /** * Split an SQL script into separate statements delimited by the provided - * delimiter string. Each individual statement will be added to the provided + * separator string. Each individual statement will be added to the + * provided {@code List}. + *

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 + * start and end 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 separator text separating each statement — typically a ';' or newline character + * @param statements the list that will contain the individual statements + * @see #splitSqlScript(String, char, List) + * @see #splitSqlScript(EncodedResource, String, String, String, String, String, List) + */ + public static void splitSqlScript(String script, String separator, List statements) throws ScriptException { + splitSqlScript(null, script, separator, 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 + * separator string. Each individual statement will be added to the provided * {@code List}. *

Within the script, the provided {@code commentPrefix} will be honored: * any text beginning with the comment prefix and extending to the end of the @@ -111,8 +142,8 @@ public abstract class ScriptUtils { * 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 separator text separating each statement — typically a ';' or + * newline character; never {@code null} * @param commentPrefix the prefix that identifies SQL line comments — * typically "--"; never {@code null} or empty * @param blockCommentStartDelimiter the start block comment delimiter; @@ -121,12 +152,12 @@ public abstract class ScriptUtils { * 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, + public static void splitSqlScript(EncodedResource resource, String script, String separator, String commentPrefix, String blockCommentStartDelimiter, String blockCommentEndDelimiter, List statements) throws ScriptException { Assert.hasText(script, "script must not be null or empty"); - Assert.notNull(delimiter, "delimiter must not be null"); + Assert.notNull(separator, "separator 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"); @@ -152,13 +183,13 @@ public abstract class ScriptUtils { inLiteral = !inLiteral; } if (!inLiteral) { - if (script.startsWith(delimiter, i)) { + if (script.startsWith(separator, 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; + i += separator.length() - 1; continue; } else if (script.startsWith(commentPrefix, i)) { @@ -215,9 +246,8 @@ public abstract class ScriptUtils { } /** - * Read a script from the provided resource, using the supplied - * comment prefix and statement separator, and build a {@code String} containing - * the lines. + * Read a script from the provided resource, using the supplied comment prefix + * and statement separator, and build a {@code String} containing the lines. *

Lines beginning 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. @@ -258,8 +288,7 @@ public abstract class ScriptUtils { String currentStatement = lineNumberReader.readLine(); StringBuilder scriptBuilder = new StringBuilder(); while (currentStatement != null) { - if (StringUtils.hasText(currentStatement) - && (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) { + if (commentPrefix != null && !currentStatement.startsWith(commentPrefix)) { if (scriptBuilder.length() > 0) { scriptBuilder.append('\n'); } @@ -267,11 +296,11 @@ public abstract class ScriptUtils { } currentStatement = lineNumberReader.readLine(); } - maybeAddSeparatorToScript(scriptBuilder, separator); + appendSeparatorToScriptIfNecessary(scriptBuilder, separator); return scriptBuilder.toString(); } - private static void maybeAddSeparatorToScript(StringBuilder scriptBuilder, String separator) { + private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuilder, String separator) { if (separator == null) { return; } @@ -305,6 +334,48 @@ public abstract class ScriptUtils { return false; } + /** + * Execute the given SQL script using default settings for separator separators, + * comment delimiters, and exception handling flags. + *

Statement separators and comments will be removed before executing + * individual statements within the supplied script. + *

Do not use this method to execute DDL if you expect rollback. + * @param connection the JDBC connection to use to execute the script; already + * configured and ready to use + * @param resource the resource to load the SQL script from; encoded with the + * current platform's default encoding + * @see #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String) + * @see #DEFAULT_COMMENT_PREFIX + * @see #DEFAULT_STATEMENT_SEPARATOR + * @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER + * @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER + */ + public static void executeSqlScript(Connection connection, Resource resource) throws SQLException, ScriptException { + executeSqlScript(connection, new EncodedResource(resource)); + } + + /** + * Execute the given SQL script using default settings for separator separators, + * comment delimiters, and exception handling flags. + *

Statement separators and comments will be removed before executing + * individual statements within the supplied script. + *

Do not use this method to execute DDL if you expect rollback. + * @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 + * @see #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String) + * @see #DEFAULT_COMMENT_PREFIX + * @see #DEFAULT_STATEMENT_SEPARATOR + * @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER + * @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER + */ + public static void executeSqlScript(Connection connection, EncodedResource resource) throws SQLException, + ScriptException { + executeSqlScript(connection, resource, false, false, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR, + DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER); + } + /** * Execute the given SQL script. *

Statement separators and comments will be removed before executing @@ -321,7 +392,8 @@ public abstract class ScriptUtils { * @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 + * {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to + * {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort * @param blockCommentStartDelimiter the start block comment delimiter; never * {@code null} or empty * @param blockCommentEndDelimiter the end block comment delimiter; never @@ -346,9 +418,9 @@ public abstract class ScriptUtils { if (separator == null) { separator = DEFAULT_STATEMENT_SEPARATOR; - if (!containsSqlScriptDelimiters(script, separator)) { - separator = "\n"; - } + } + if (!containsSqlScriptDelimiters(script, separator)) { + separator = FALLBACK_STATEMENT_SEPARATOR; } splitSqlScript(resource, script, separator, commentPrefix, blockCommentStartDelimiter, diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/AbstractDatabaseInitializationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/AbstractDatabaseInitializationTests.java new file mode 100644 index 0000000000..7d85f92cb2 --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/AbstractDatabaseInitializationTests.java @@ -0,0 +1,64 @@ +/* + * 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.junit.After; +import org.junit.Before; +import org.springframework.core.io.ClassRelativeResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Abstract base class for integration tests involving database initialization. + * + * @author Sam Brannen + * @since 4.0.3 + */ +public abstract class AbstractDatabaseInitializationTests { + + private final ClassRelativeResourceLoader resourceLoader = new ClassRelativeResourceLoader(getClass()); + + protected EmbeddedDatabase db; + protected JdbcTemplate jdbcTemplate; + + + @Before + public void setUp() { + db = new EmbeddedDatabaseBuilder().setType(getEmbeddedDatabaseType()).build(); + jdbcTemplate = new JdbcTemplate(db); + } + + @After + public void shutDown() { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clear(); + TransactionSynchronizationManager.unbindResource(db); + } + db.shutdown(); + } + + protected abstract EmbeddedDatabaseType getEmbeddedDatabaseType(); + + protected Resource resource(String path) { + return resourceLoader.getResource(path); + } + +} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/AbstractDatabasePopulatorTests.java similarity index 89% rename from spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java rename to spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/AbstractDatabasePopulatorTests.java index 571b744384..f26116b54a 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/AbstractDatabasePopulatorTests.java @@ -19,14 +19,9 @@ package org.springframework.jdbc.datasource.init; import java.sql.Connection; import java.sql.SQLException; -import org.junit.After; import org.junit.Test; -import org.springframework.core.io.ClassRelativeResourceLoader; import org.springframework.core.io.Resource; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceUtils; -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.*; @@ -34,57 +29,16 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** + * Abstract base class for integration tests for {@link ResourceDatabasePopulator}. + * * @author Dave Syer * @author Sam Brannen * @author Oliver Gierke */ -public class DatabasePopulatorTests { - - private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build(); +public abstract class AbstractDatabasePopulatorTests extends AbstractDatabaseInitializationTests { private final ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); - private final ClassRelativeResourceLoader resourceLoader = new ClassRelativeResourceLoader(getClass()); - - private final JdbcTemplate jdbcTemplate = new JdbcTemplate(db); - - - private void assertTestDatabaseCreated() { - assertTestDatabaseCreated("Keith"); - } - - private void assertTestDatabaseCreated(String name) { - assertEquals(name, jdbcTemplate.queryForObject("select NAME from T_TEST", String.class)); - } - - private void assertUsersDatabaseCreated(String... lastNames) { - for (String lastName : lastNames) { - assertThat("Did not find user with last name [" + lastName + "].", - jdbcTemplate.queryForObject("select count(0) from users where last_name = ?", Integer.class, lastName), - equalTo(1)); - } - } - - private Resource resource(String path) { - return resourceLoader.getResource(path); - } - - private Resource defaultSchema() { - return resource("db-schema.sql"); - } - - private Resource usersSchema() { - return resource("users-schema.sql"); - } - - @After - public void shutDown() { - if (TransactionSynchronizationManager.isSynchronizationActive()) { - TransactionSynchronizationManager.clear(); - TransactionSynchronizationManager.unbindResource(db); - } - db.shutdown(); - } @Test public void buildWithCommentsAndFailedDrop() throws Exception { @@ -226,4 +180,28 @@ public class DatabasePopulatorTests { DatabasePopulatorUtils.execute(databasePopulator, db); } + private void assertTestDatabaseCreated() { + assertTestDatabaseCreated("Keith"); + } + + private void assertTestDatabaseCreated(String name) { + assertEquals(name, jdbcTemplate.queryForObject("select NAME from T_TEST", String.class)); + } + + private void assertUsersDatabaseCreated(String... lastNames) { + for (String lastName : lastNames) { + assertThat("Did not find user with last name [" + lastName + "].", + jdbcTemplate.queryForObject("select count(0) from users where last_name = ?", Integer.class, lastName), + equalTo(1)); + } + } + + private Resource defaultSchema() { + return resource("db-schema.sql"); + } + + private Resource usersSchema() { + return resource("users-schema.sql"); + } + } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/H2DatabasePopulatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/H2DatabasePopulatorTests.java new file mode 100644 index 0000000000..699ea75b1a --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/H2DatabasePopulatorTests.java @@ -0,0 +1,31 @@ +/* + * 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.jdbc.datasource.embedded.EmbeddedDatabaseType; + +/** + * @author Sam Brannen + * @since 4.0.3 + */ +public class H2DatabasePopulatorTests extends AbstractDatabasePopulatorTests { + + protected EmbeddedDatabaseType getEmbeddedDatabaseType() { + return EmbeddedDatabaseType.H2; + } + +} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/HsqlDatabasePopulatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/HsqlDatabasePopulatorTests.java new file mode 100644 index 0000000000..9ab79b6054 --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/HsqlDatabasePopulatorTests.java @@ -0,0 +1,31 @@ +/* + * 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.jdbc.datasource.embedded.EmbeddedDatabaseType; + +/** + * @author Sam Brannen + * @since 4.0.3 + */ +public class HsqlDatabasePopulatorTests extends AbstractDatabasePopulatorTests { + + protected EmbeddedDatabaseType getEmbeddedDatabaseType() { + return EmbeddedDatabaseType.HSQL; + } + +} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java new file mode 100644 index 0000000000..f7d8f18092 --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsIntegrationTests.java @@ -0,0 +1,58 @@ +/* + * 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.SQLException; + +import org.junit.Test; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * Integration tests for {@link ScriptUtils}. + * + * @author Sam Brannen + * @see ScriptUtilsUnitTests + * @since 4.0.3 + */ +public class ScriptUtilsIntegrationTests extends AbstractDatabaseInitializationTests { + + protected EmbeddedDatabaseType getEmbeddedDatabaseType() { + return EmbeddedDatabaseType.HSQL; + } + + @Test + public void executeSqlScript() throws SQLException { + ScriptUtils.executeSqlScript(db.getConnection(), resource("users-schema.sql")); + ScriptUtils.executeSqlScript(db.getConnection(), resource("test-data-with-multi-line-comments.sql")); + + 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)); + } + } + +} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java similarity index 61% rename from spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java rename to spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java index dff78a50b9..6df2eb1793 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java @@ -16,44 +16,27 @@ 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.*; +import static org.springframework.jdbc.datasource.init.ScriptUtils.*; /** - * Unit and integration tests for {@link ScriptUtils}. + * Unit tests for {@link ScriptUtils}. * * @author Thomas Risberg * @author Sam Brannen * @author Phillip Webb * @author Chris Baldwin + * @see ScriptUtilsIntegrationTests + * @since 4.0.3 */ -public class ScriptUtilsTests { - - private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build(); - - - @After - public void shutDown() { - if (TransactionSynchronizationManager.isSynchronizationActive()) { - TransactionSynchronizationManager.clear(); - TransactionSynchronizationManager.unbindResource(db); - } - db.shutdown(); - } +public class ScriptUtilsUnitTests { @Test public void splitSqlScriptDelimitedWithSemicolon() { @@ -66,7 +49,7 @@ public class ScriptUtilsTests { char delim = ';'; String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim; List statements = new ArrayList(); - ScriptUtils.splitSqlScript(script, delim, statements); + 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)); @@ -81,7 +64,7 @@ public class ScriptUtilsTests { char delim = '\n'; String script = statement1 + delim + statement2 + delim + statement3 + delim; List statements = new ArrayList(); - ScriptUtils.splitSqlScript(script, delim, statements); + 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)); @@ -89,14 +72,40 @@ public class ScriptUtilsTests { } @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 = ';'; + public void splitSqlScriptDelimitedWithNewLineButDefaultDelimiterSpecified() { + String statement1 = "do something"; + String statement2 = "do something else"; + char delim = '\n'; + String script = statement1 + delim + statement2 + delim; List statements = new ArrayList(); - ScriptUtils.splitSqlScript(script, delim, statements); + splitSqlScript(script, DEFAULT_STATEMENT_SEPARATOR, statements); + assertEquals("wrong number of statements", 1, statements.size()); + assertEquals("script should have been 'stripped' but not actually 'split'", script.replace('\n', ' '), + statements.get(0)); + } + + /** + * See SPR-11560 + */ + @Test + public void readAndSplitScriptWithMultipleNewlinesAsSeparator() throws Exception { + String script = readScript("db-test-data-multi-newline.sql"); + List statements = new ArrayList(); + splitSqlScript(script, "\n\n", statements); + + String statement1 = "insert into T_TEST (NAME) values ('Keith')"; + String statement2 = "insert into T_TEST (NAME) values ('Dave')"; + + 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 readAndSplitScriptContainingComments() throws Exception { + String script = readScript("test-data-with-comments.sql"); + List statements = new ArrayList(); + splitSqlScript(script, ';', 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)"; @@ -112,18 +121,13 @@ public class ScriptUtilsTests { } /** - * See SPR-10330 + * See SPR-10330 */ @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 = ';'; + String script = readScript("test-data-with-comments-and-leading-tabs.sql"); List statements = new ArrayList(); - ScriptUtils.splitSqlScript(script, delim, statements); + splitSqlScript(script, ';', 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)"; @@ -136,18 +140,13 @@ public class ScriptUtilsTests { } /** - * See SPR-9531 + * See SPR-9531 */ @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 = ';'; + String script = readScript("test-data-with-multi-line-comments.sql"); List statements = new ArrayList(); - ScriptUtils.splitSqlScript(script, delim, statements); + splitSqlScript(script, ';', 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' )"; @@ -159,38 +158,15 @@ public class ScriptUtilsTests { @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")); + assertTrue("test with ';' is wrong", !containsSqlScriptDelimiters("select 1\n select ';'", ";")); + assertTrue("test with delimiter ; is wrong", containsSqlScriptDelimiters("select 1; select 2", ";")); + assertTrue("test with '\\n' is wrong", !containsSqlScriptDelimiters("select 1; select '\\n\n';", "\n")); + assertTrue("test with delimiter \\n is wrong", 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)); - } + private String readScript(String path) throws Exception { + EncodedResource resource = new EncodedResource(new ClassPathResource(path, getClass())); + return ScriptUtils.readScript(resource); } }