diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java
index 24cfb59722..e4c3abd262 100644
--- a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java
+++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2019 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,11 +16,15 @@
package org.springframework.test.context.jdbc;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.style.ToStringCreator;
import org.springframework.jdbc.datasource.init.ScriptUtils;
+import org.springframework.lang.Nullable;
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
import org.springframework.util.Assert;
@@ -37,6 +41,11 @@ import org.springframework.util.Assert;
*/
class MergedSqlConfig {
+ private static final String COMMENT_PREFIX = "commentPrefix";
+
+ private static final String COMMENT_PREFIXES = "commentPrefixes";
+
+
private final String dataSource;
private final String transactionManager;
@@ -47,7 +56,7 @@ class MergedSqlConfig {
private final String separator;
- private final String commentPrefix;
+ private final String[] commentPrefixes;
private final String blockCommentStartDelimiter;
@@ -68,38 +77,53 @@ class MergedSqlConfig {
Assert.notNull(localSqlConfig, "Local @SqlConfig must not be null");
Assert.notNull(testClass, "testClass must not be null");
+ AnnotationAttributes mergedAttributes;
+ AnnotationAttributes localAttributes = AnnotationUtils.getAnnotationAttributes(localSqlConfig, false, false);
+ // Enforce comment prefix aliases within the local @SqlConfig.
+ enforceCommentPrefixAliases(localAttributes);
+
// Get global attributes, if any.
- AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
+ AnnotationAttributes globalAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
testClass, SqlConfig.class.getName(), false, false);
- // Override global attributes with local attributes.
- if (attributes != null) {
- for (String key : attributes.keySet()) {
- Object value = AnnotationUtils.getValue(localSqlConfig, key);
- if (value != null) {
- // Is the value explicit (i.e., not a 'default')?
- if (!value.equals("") && value != TransactionMode.DEFAULT && value != ErrorMode.DEFAULT) {
- attributes.put(key, value);
+ if (globalAttributes != null) {
+ // Enforce comment prefix aliases within the global @SqlConfig.
+ enforceCommentPrefixAliases(globalAttributes);
+
+ for (String key : globalAttributes.keySet()) {
+ Object value = localAttributes.get(key);
+ if (isExplicitValue(value)) {
+ // Override global attribute with local attribute.
+ globalAttributes.put(key, value);
+
+ // Ensure comment prefix aliases are honored during the merge.
+ if (key.equals(COMMENT_PREFIX) && isEmptyArray(localAttributes.get(COMMENT_PREFIXES))) {
+ globalAttributes.put(COMMENT_PREFIXES, value);
+ }
+ else if (key.equals(COMMENT_PREFIXES) && isEmptyString(localAttributes.get(COMMENT_PREFIX))) {
+ globalAttributes.put(COMMENT_PREFIX, value);
}
}
}
+ mergedAttributes = globalAttributes;
}
else {
// Otherwise, use local attributes only.
- attributes = AnnotationUtils.getAnnotationAttributes(localSqlConfig, false, false);
+ mergedAttributes = localAttributes;
}
- this.dataSource = attributes.getString("dataSource");
- this.transactionManager = attributes.getString("transactionManager");
- this.transactionMode = getEnum(attributes, "transactionMode", TransactionMode.DEFAULT, TransactionMode.INFERRED);
- this.encoding = attributes.getString("encoding");
- this.separator = getString(attributes, "separator", ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
- this.commentPrefix = getString(attributes, "commentPrefix", ScriptUtils.DEFAULT_COMMENT_PREFIX);
- this.blockCommentStartDelimiter = getString(attributes, "blockCommentStartDelimiter",
+ this.dataSource = mergedAttributes.getString("dataSource");
+ this.transactionManager = mergedAttributes.getString("transactionManager");
+ this.transactionMode = getEnum(mergedAttributes, "transactionMode", TransactionMode.DEFAULT,
+ TransactionMode.INFERRED);
+ this.encoding = mergedAttributes.getString("encoding");
+ this.separator = getString(mergedAttributes, "separator", ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
+ this.commentPrefixes = getCommentPrefixes(mergedAttributes);
+ this.blockCommentStartDelimiter = getString(mergedAttributes, "blockCommentStartDelimiter",
ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER);
- this.blockCommentEndDelimiter = getString(attributes, "blockCommentEndDelimiter",
+ this.blockCommentEndDelimiter = getString(mergedAttributes, "blockCommentEndDelimiter",
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
- this.errorMode = getEnum(attributes, "errorMode", ErrorMode.DEFAULT, ErrorMode.FAIL_ON_ERROR);
+ this.errorMode = getEnum(mergedAttributes, "errorMode", ErrorMode.DEFAULT, ErrorMode.FAIL_ON_ERROR);
}
/**
@@ -138,10 +162,11 @@ class MergedSqlConfig {
}
/**
- * @see SqlConfig#commentPrefix()
+ * @see SqlConfig#commentPrefixes()
+ * @since 5.2
*/
- String getCommentPrefix() {
- return this.commentPrefix;
+ String[] getCommentPrefixes() {
+ return this.commentPrefixes;
}
/**
@@ -176,7 +201,7 @@ class MergedSqlConfig {
.append("transactionMode", this.transactionMode)
.append("encoding", this.encoding)
.append("separator", this.separator)
- .append("commentPrefix", this.commentPrefix)
+ .append("commentPrefixes", this.commentPrefixes)
.append("blockCommentStartDelimiter", this.blockCommentStartDelimiter)
.append("blockCommentEndDelimiter", this.blockCommentEndDelimiter)
.append("errorMode", this.errorMode)
@@ -202,4 +227,58 @@ class MergedSqlConfig {
return value;
}
+ private static void enforceCommentPrefixAliases(AnnotationAttributes attributes) {
+ String commentPrefix = attributes.getString(COMMENT_PREFIX);
+ String[] commentPrefixes = attributes.getStringArray(COMMENT_PREFIXES);
+
+ boolean explicitCommentPrefix = !commentPrefix.isEmpty();
+ boolean explicitCommentPrefixes = (commentPrefixes.length != 0);
+ Assert.isTrue(!(explicitCommentPrefix && explicitCommentPrefixes),
+ "You may declare the 'commentPrefix' or 'commentPrefixes' attribute in @SqlConfig but not both");
+
+ if (explicitCommentPrefix) {
+ Assert.hasText(commentPrefix, "@SqlConfig(commentPrefix) must contain text");
+ attributes.put(COMMENT_PREFIXES, new String[] { commentPrefix });
+ }
+ else if (explicitCommentPrefixes) {
+ for (String prefix : commentPrefixes) {
+ Assert.hasText(prefix, "@SqlConfig(commentPrefixes) must not contain empty prefixes");
+ }
+ attributes.put(COMMENT_PREFIX, commentPrefixes);
+ }
+ else {
+ // We know commentPrefixes is an empty array, so make sure commentPrefix
+ // is set to that as well in order to honor the alias contract.
+ attributes.put(COMMENT_PREFIX, commentPrefixes);
+ }
+ }
+
+ private static String[] getCommentPrefixes(AnnotationAttributes attributes) {
+ String[] commentPrefix = attributes.getStringArray(COMMENT_PREFIX);
+ String[] commentPrefixes = attributes.getStringArray(COMMENT_PREFIXES);
+
+ Assert.state(Arrays.equals(commentPrefix, commentPrefixes),
+ "Failed to properly handle 'commentPrefix' and 'commentPrefixes' aliases");
+
+ return (commentPrefixes.length != 0 ? commentPrefixes : ScriptUtils.DEFAULT_COMMENT_PREFIXES);
+ }
+
+ /**
+ * Determine if the supplied value is an explicit value (i.e., not a default).
+ */
+ private static boolean isExplicitValue(@Nullable Object value) {
+ return !(isEmptyString(value) ||
+ isEmptyArray(value) ||
+ value == TransactionMode.DEFAULT ||
+ value == ErrorMode.DEFAULT);
+ }
+
+ private static boolean isEmptyString(@Nullable Object value) {
+ return (value instanceof String && ((String) value).isEmpty());
+ }
+
+ private static boolean isEmptyArray(@Nullable Object value) {
+ return (value != null && value.getClass().isArray() && Array.getLength(value) == 0);
+ }
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java
index 8b9ddf3aca..c3487bdf3b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java
+++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java
@@ -42,16 +42,17 @@ import java.lang.annotation.Target;
* is unfortunately not possible to assign a value of {@code null} to an annotation
* attribute. Thus, in order to support overrides of inherited global
* configuration, {@code @SqlConfig} attributes have an explicit
- * {@code default} value of either {@code ""} for Strings or {@code DEFAULT} for
- * Enums. This approach allows local declarations of {@code @SqlConfig} to
- * selectively override individual attributes from global declarations of
- * {@code @SqlConfig} by providing a value other than {@code ""} or {@code DEFAULT}.
+ * {@code default} value of either {@code ""} for Strings, {}
for
+ * arrays, or {@code DEFAULT} for Enums. This approach allows local declarations
+ * of {@code @SqlConfig} to selectively override individual attributes from global
+ * declarations of {@code @SqlConfig} by providing a value other than {@code ""},
+ * {}
, or {@code DEFAULT}.
*
*
Global {@code @SqlConfig} attributes are inherited whenever local
* {@code @SqlConfig} attributes do not supply an explicit value other than
- * {@code ""} or {@code DEFAULT}. Explicit local configuration therefore
- * overrides global configuration.
+ * {@code ""}, {}
, or {@code DEFAULT}. Explicit local configuration
+ * therefore overrides global configuration.
*
* @author Sam Brannen
* @author Tadaya Tsuyukubo
@@ -145,10 +146,26 @@ public @interface SqlConfig {
/**
* The prefix that identifies single-line comments within the SQL scripts.
*
Implicitly defaults to {@code "--"}. + *
This attribute may not be used in conjunction with + * {@link #commentPrefixes commentPrefixes}, but it may be used instead of + * {@link #commentPrefixes commentPrefixes}. * @see org.springframework.jdbc.datasource.init.ScriptUtils#DEFAULT_COMMENT_PREFIX + * @see #commentPrefixes */ String commentPrefix() default ""; + /** + * The prefixes that identify single-line comments within the SQL scripts. + *
Implicitly defaults to {@code ["--"]}. + *
This attribute may not be used in conjunction with + * {@link #commentPrefix commentPrefix}, but it may be used instead of + * {@link #commentPrefix commentPrefix}. + * @see org.springframework.jdbc.datasource.init.ScriptUtils#DEFAULT_COMMENT_PREFIXES + * @see #commentPrefix + * @since 5.2 + */ + String[] commentPrefixes() default {}; + /** * The start delimiter that identifies block comments within the SQL scripts. *
Implicitly defaults to {@code "/*"}. diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index f824a363c8..1a2ed90857 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -267,7 +267,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding()); populator.setSeparator(mergedSqlConfig.getSeparator()); - populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix()); + populator.setCommentPrefixes(mergedSqlConfig.getCommentPrefixes()); populator.setBlockCommentStartDelimiter(mergedSqlConfig.getBlockCommentStartDelimiter()); populator.setBlockCommentEndDelimiter(mergedSqlConfig.getBlockCommentEndDelimiter()); populator.setContinueOnError(mergedSqlConfig.getErrorMode() == ErrorMode.CONTINUE_ON_ERROR); diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java index 968084d2e4..0011f26959 100644 --- a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -27,6 +27,6 @@ import org.springframework.test.context.jdbc.SqlConfig; */ @ContextConfiguration(classes = EmptyDatabaseConfig.class) @DirtiesContext -@SqlConfig(commentPrefix = "`", blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@") +@SqlConfig(commentPrefixes = { "`", "%%" }, blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@") interface SqlConfigTestInterface { } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java index e15313f4cd..6bbaa3a0fc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/CustomScriptSyntaxSqlScriptsTests.java @@ -38,7 +38,7 @@ public class CustomScriptSyntaxSqlScriptsTests extends AbstractTransactionalJUni @Test @Sql("schema.sql") @Sql(scripts = "data-add-users-with-custom-script-syntax.sql",// - config = @SqlConfig(commentPrefix = "`", blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@")) + config = @SqlConfig(commentPrefixes = { "`", "%%" }, blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@")) public void methodLevelScripts() { assertNumUsers(3); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/GlobalCustomScriptSyntaxSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/GlobalCustomScriptSyntaxSqlScriptsTests.java index 17038cb48d..a7eb393153 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/GlobalCustomScriptSyntaxSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/GlobalCustomScriptSyntaxSqlScriptsTests.java @@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; */ @ContextConfiguration(classes = EmptyDatabaseConfig.class) @DirtiesContext -@SqlConfig(commentPrefix = "`", blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@") +@SqlConfig(commentPrefixes = { "`", "%%" }, blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@") public class GlobalCustomScriptSyntaxSqlScriptsTests extends AbstractTransactionalJUnit4SpringContextTests { @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java index 10a34ed270..0180a28c76 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java @@ -21,9 +21,11 @@ import java.lang.reflect.Method; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER; import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER; -import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_COMMENT_PREFIX; +import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_COMMENT_PREFIXES; import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_STATEMENT_SEPARATOR; import static org.springframework.test.context.jdbc.SqlConfig.ErrorMode.CONTINUE_ON_ERROR; import static org.springframework.test.context.jdbc.SqlConfig.ErrorMode.FAIL_ON_ERROR; @@ -39,17 +41,50 @@ import static org.springframework.test.context.jdbc.SqlConfig.TransactionMode.IS */ public class MergedSqlConfigTests { - private void assertDefaults(MergedSqlConfig cfg) { - assertThat(cfg).isNotNull(); - assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); - assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); - assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); - assertThat(cfg.getEncoding()).as("encoding").isEqualTo(""); - assertThat(cfg.getSeparator()).as("separator").isEqualTo(DEFAULT_STATEMENT_SEPARATOR); - assertThat(cfg.getCommentPrefix()).as("commentPrefix").isEqualTo(DEFAULT_COMMENT_PREFIX); - assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); - assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); - assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(FAIL_ON_ERROR); + @Test + public void nullLocalSqlConfig() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(null, getClass())) + .withMessage("Local @SqlConfig must not be null"); + } + + @Test + public void nullTestClass() { + SqlConfig sqlConfig = GlobalConfigClass.class.getAnnotation(SqlConfig.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(sqlConfig, null)) + .withMessage("testClass must not be null"); + } + + @Test + public void localConfigWithEmptyCommentPrefix() throws Exception { + Method method = getClass().getMethod("localConfigMethodWithEmptyCommentPrefix"); + SqlConfig sqlConfig = method.getAnnotation(Sql.class).config(); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(sqlConfig, getClass())) + .withMessage("@SqlConfig(commentPrefix) must contain text"); + } + + @Test + public void localConfigWithEmptyCommentPrefixes() throws Exception { + Method method = getClass().getMethod("localConfigMethodWithEmptyCommentPrefixes"); + SqlConfig sqlConfig = method.getAnnotation(Sql.class).config(); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(sqlConfig, getClass())) + .withMessage("@SqlConfig(commentPrefixes) must not contain empty prefixes"); + } + + @Test + public void localConfigWithDuplicatedCommentPrefixes() throws Exception { + Method method = getClass().getMethod("localConfigMethodWithDuplicatedCommentPrefixes"); + SqlConfig sqlConfig = method.getAnnotation(Sql.class).config(); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(sqlConfig, getClass())) + .withMessage("You may declare the 'commentPrefix' or 'commentPrefixes' attribute in @SqlConfig but not both"); } @Test @@ -61,28 +96,43 @@ public class MergedSqlConfigTests { } @Test - public void globalConfigWithDefaults() throws Exception { - Method method = GlobalConfigWithDefaultsClass.class.getMethod("globalConfigMethod"); + public void localConfigWithCustomValues() throws Exception { + Method method = getClass().getMethod("localConfigMethodWithCustomValues"); SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); - MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigWithDefaultsClass.class); - assertDefaults(cfg); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass()); + + assertSoftly(softly -> { + softly.assertThat(cfg).isNotNull(); + softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo("ds"); + softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo("txMgr"); + softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(ISOLATED); + softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo("enigma"); + softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo("\n"); + softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("`")); + softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo("<<"); + softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(">>"); + softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(IGNORE_FAILED_DROPS); + }); } @Test - public void localConfigWithCustomValues() throws Exception { - Method method = getClass().getMethod("localConfigMethodWithCustomValues"); + public void localConfigWithCustomCommentPrefixes() throws Exception { + Method method = getClass().getMethod("localConfigMethodWithCustomCommentPrefixes"); SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass()); + assertThat(cfg).isNotNull(); - assertThat(cfg.getDataSource()).as("dataSource").isEqualTo("ds"); - assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo("txMgr"); - assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(ISOLATED); - assertThat(cfg.getEncoding()).as("encoding").isEqualTo("enigma"); - assertThat(cfg.getSeparator()).as("separator").isEqualTo("\n"); - assertThat(cfg.getCommentPrefix()).as("commentPrefix").isEqualTo("`"); - assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo("<<"); - assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(">>"); - assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(IGNORE_FAILED_DROPS); + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("`")); + } + + @Test + public void localConfigWithMultipleCommentPrefixes() throws Exception { + Method method = getClass().getMethod("localConfigMethodWithMultipleCommentPrefixes"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass()); + + assertThat(cfg).isNotNull(); + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("`", "--")); } @Test @@ -90,6 +140,7 @@ public class MergedSqlConfigTests { Method method = getClass().getMethod("localConfigMethodWithContinueOnError"); SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass()); + assertThat(cfg).isNotNull(); assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(CONTINUE_ON_ERROR); } @@ -99,25 +150,64 @@ public class MergedSqlConfigTests { Method method = getClass().getMethod("localConfigMethodWithIgnoreFailedDrops"); SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, getClass()); + assertThat(cfg).isNotNull(); assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(IGNORE_FAILED_DROPS); } + @Test + public void globalConfigWithEmptyCommentPrefix() throws Exception { + SqlConfig sqlConfig = GlobalConfigWithWithEmptyCommentPrefixClass.class.getAnnotation(SqlConfig.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(sqlConfig, getClass())) + .withMessage("@SqlConfig(commentPrefix) must contain text"); + } + + @Test + public void globalConfigWithEmptyCommentPrefixes() throws Exception { + SqlConfig sqlConfig = GlobalConfigWithWithEmptyCommentPrefixesClass.class.getAnnotation(SqlConfig.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(sqlConfig, getClass())) + .withMessage("@SqlConfig(commentPrefixes) must not contain empty prefixes"); + } + + @Test + public void globalConfigWithDuplicatedCommentPrefixes() throws Exception { + SqlConfig sqlConfig = GlobalConfigWithWithDuplicatedCommentPrefixesClass.class.getAnnotation(SqlConfig.class); + + assertThatIllegalArgumentException() + .isThrownBy(() -> new MergedSqlConfig(sqlConfig, getClass())) + .withMessage("You may declare the 'commentPrefix' or 'commentPrefixes' attribute in @SqlConfig but not both"); + } + + @Test + public void globalConfigWithDefaults() throws Exception { + Method method = GlobalConfigWithDefaultsClass.class.getMethod("globalConfigMethod"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigWithDefaultsClass.class); + assertDefaults(cfg); + } + @Test public void globalConfig() throws Exception { Method method = GlobalConfigClass.class.getMethod("globalConfigMethod"); SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigClass.class); - assertThat(cfg).isNotNull(); - assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); - assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); - assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); - assertThat(cfg.getEncoding()).as("encoding").isEqualTo("global"); - assertThat(cfg.getSeparator()).as("separator").isEqualTo("\n"); - assertThat(cfg.getCommentPrefix()).as("commentPrefix").isEqualTo(DEFAULT_COMMENT_PREFIX); - assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); - assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); - assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(IGNORE_FAILED_DROPS); + + assertSoftly(softly -> { + softly.assertThat(cfg).isNotNull(); + softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); + softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); + softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); + softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo("global"); + softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo("\n"); + softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("`", "--")); + softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); + softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); + softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(IGNORE_FAILED_DROPS); + }); } @Test @@ -126,20 +216,79 @@ public class MergedSqlConfigTests { SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigClass.class); - assertThat(cfg).isNotNull(); - assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); - assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); - assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); - assertThat(cfg.getEncoding()).as("encoding").isEqualTo("local"); - assertThat(cfg.getSeparator()).as("separator").isEqualTo("@@"); - assertThat(cfg.getCommentPrefix()).as("commentPrefix").isEqualTo(DEFAULT_COMMENT_PREFIX); - assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); - assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); - assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(CONTINUE_ON_ERROR); + assertSoftly(softly -> { + softly.assertThat(cfg).isNotNull(); + softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); + softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); + softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); + softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo("local"); + softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo("@@"); + softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); + softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); + softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); + softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(CONTINUE_ON_ERROR); + }); + } + + @Test + public void globalConfigWithCommentPrefixAndLocalOverrides() throws Exception { + Class> testClass = GlobalConfigWithPrefixClass.class; + + Method method = testClass.getMethod("commentPrefixesOverrideCommentPrefix"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#", "@")); + + method = testClass.getMethod("commentPrefixOverridesCommentPrefix"); + localSqlConfig = method.getAnnotation(Sql.class).config(); + cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); + } + + @Test + public void globalConfigWithCommentPrefixesAndLocalOverrides() throws Exception { + Class> testClass = GlobalConfigWithPrefixesClass.class; + + Method method = testClass.getMethod("commentPrefixesOverrideCommentPrefixes"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#", "@")); + + method = testClass.getMethod("commentPrefixOverridesCommentPrefixes"); + localSqlConfig = method.getAnnotation(Sql.class).config(); + cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); + } + + private void assertDefaults(MergedSqlConfig cfg) { + assertSoftly(softly -> { + softly.assertThat(cfg).isNotNull(); + softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); + softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); + softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); + softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo(""); + softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo(DEFAULT_STATEMENT_SEPARATOR); + softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(DEFAULT_COMMENT_PREFIXES); + softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); + softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); + softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(FAIL_ON_ERROR); + }); + } + + private static String[] array(String... elements) { + return elements; } // ------------------------------------------------------------------------- + @Sql(config = @SqlConfig(commentPrefix = "#", commentPrefixes = "#" )) + public static void localConfigMethodWithDuplicatedCommentPrefixes() { + } + @Sql public static void localConfigMethodWithDefaults() { } @@ -148,6 +297,22 @@ public class MergedSqlConfigTests { public static void localConfigMethodWithCustomValues() { } + @Sql(config = @SqlConfig(commentPrefix = " " )) + public static void localConfigMethodWithEmptyCommentPrefix() { + } + + @Sql(config = @SqlConfig(commentPrefixes = { "--", " " })) + public static void localConfigMethodWithEmptyCommentPrefixes() { + } + + @Sql(config = @SqlConfig(commentPrefixes = "`")) + public static void localConfigMethodWithCustomCommentPrefixes() { + } + + @Sql(config = @SqlConfig(commentPrefixes = { "`", "--" })) + public static void localConfigMethodWithMultipleCommentPrefixes() { + } + @Sql(config = @SqlConfig(errorMode = CONTINUE_ON_ERROR)) public static void localConfigMethodWithContinueOnError() { } @@ -156,25 +321,60 @@ public class MergedSqlConfigTests { public static void localConfigMethodWithIgnoreFailedDrops() { } + @SqlConfig(commentPrefix = " ") + public static class GlobalConfigWithWithEmptyCommentPrefixClass { + } + + @SqlConfig(commentPrefixes = { "--", " " }) + public static class GlobalConfigWithWithEmptyCommentPrefixesClass { + } + + @SqlConfig(commentPrefix = "#", commentPrefixes = "#") + public static class GlobalConfigWithWithDuplicatedCommentPrefixesClass { + } @SqlConfig public static class GlobalConfigWithDefaultsClass { - @Sql("foo.sql") + @Sql public void globalConfigMethod() { } } - @SqlConfig(encoding = "global", separator = "\n", errorMode = IGNORE_FAILED_DROPS) + @SqlConfig(encoding = "global", separator = "\n", commentPrefixes = { "`", "--" }, errorMode = IGNORE_FAILED_DROPS) public static class GlobalConfigClass { - @Sql("foo.sql") + @Sql public void globalConfigMethod() { } - @Sql(scripts = "foo.sql", config = @SqlConfig(encoding = "local", separator = "@@", errorMode = CONTINUE_ON_ERROR)) + @Sql(config = @SqlConfig(encoding = "local", separator = "@@", commentPrefix = "#", errorMode = CONTINUE_ON_ERROR)) public void globalConfigWithLocalOverridesMethod() { } } + @SqlConfig(commentPrefix = "`") + public static class GlobalConfigWithPrefixClass { + + @Sql(config = @SqlConfig(commentPrefixes = { "#", "@" })) + public void commentPrefixesOverrideCommentPrefix() { + } + + @Sql(config = @SqlConfig(commentPrefix = "#")) + public void commentPrefixOverridesCommentPrefix() { + } + } + + @SqlConfig(commentPrefixes = { "`", "--" }) + public static class GlobalConfigWithPrefixesClass { + + @Sql(config = @SqlConfig(commentPrefixes = { "#", "@" })) + public void commentPrefixesOverrideCommentPrefixes() { + } + + @Sql(config = @SqlConfig(commentPrefix = "#")) + public void commentPrefixOverridesCommentPrefixes() { + } + } + } diff --git a/spring-test/src/test/resources/org/springframework/test/context/jdbc/data-add-users-with-custom-script-syntax.sql b/spring-test/src/test/resources/org/springframework/test/context/jdbc/data-add-users-with-custom-script-syntax.sql index 664a6998e5..ec6ff77fb9 100644 --- a/spring-test/src/test/resources/org/springframework/test/context/jdbc/data-add-users-with-custom-script-syntax.sql +++ b/spring-test/src/test/resources/org/springframework/test/context/jdbc/data-add-users-with-custom-script-syntax.sql @@ -19,4 +19,7 @@ VALUES('Dilbert') INSERT INTO user VALUES('Dogbert')@@ + +%% another custom single-line comment + INSERT INTO user VALUES('Catbert')@@ diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index d9e37f2762..4de47ea1cc 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -4190,12 +4190,12 @@ documented in the javadoc of the corresponding attribute. Due to the rules defin annotation attributes in the Java Language Specification, it is, unfortunately, not possible to assign a value of `null` to an annotation attribute. Thus, in order to support overrides of inherited global configuration, `@SqlConfig` attributes have an -explicit default value of either `""` (for Strings) or `DEFAULT` (for enumerations). This -approach lets local declarations of `@SqlConfig` selectively override individual -attributes from global declarations of `@SqlConfig` by providing a value other than `""` -or `DEFAULT`. Global `@SqlConfig` attributes are inherited whenever local `@SqlConfig` -attributes do not supply an explicit value other than `""` or `DEFAULT`. Explicit local -configuration, therefore, overrides global configuration. +explicit default value of either `""` (for Strings), `{}` (for arrays), or `DEFAULT` (for +enumerations). This approach lets local declarations of `@SqlConfig` selectively override +individual attributes from global declarations of `@SqlConfig` by providing a value other +than `""`, `{}`, or `DEFAULT`. Global `@SqlConfig` attributes are inherited whenever +local `@SqlConfig` attributes do not supply an explicit value other than `""`, `{}`, or +`DEFAULT`. Explicit local configuration, therefore, overrides global configuration. The configuration options provided by `@Sql` and `@SqlConfig` are equivalent to those supported by `ScriptUtils` and `ResourceDatabasePopulator` but are a superset of those