diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-05-18 16:02:12 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-05-19 15:35:58 +0200 |
commit | 4478d4093e636acf7811e96b08fb9ecdbe22eb0f (patch) | |
tree | 18af88b01b82f3d655becd07a87106f5dfb3dfba | |
parent | bc9b9edb7c887a74c47c59d615c48ae7024ab392 (diff) | |
download | sonarqube-4478d4093e636acf7811e96b08fb9ecdbe22eb0f.tar.gz sonarqube-4478d4093e636acf7811e96b08fb9ecdbe22eb0f.zip |
SONAR-7649 do not repair db collation outside migration
17 files changed, 223 insertions, 103 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAtStartup.java b/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAtStartup.java index 966e676a509..91da7bd33c0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAtStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAtStartup.java @@ -23,6 +23,8 @@ import org.picocontainer.Startable; import org.sonar.api.platform.ServerUpgradeStatus; import org.sonar.db.charset.DatabaseCharsetChecker; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; + /** * Checks charset of all existing database columns at startup, before executing db migrations. This requires * to be defined in platform level 2 ({@link org.sonar.server.platform.platformlevel.PlatformLevel2}). @@ -48,11 +50,10 @@ public class CheckDatabaseCharsetAtStartup implements Startable { } protected final void check() { - boolean enforceUtf8 = getUpgradeStatus().isFreshInstall(); - charsetChecker.check(enforceUtf8); - } - - protected final ServerUpgradeStatus getUpgradeStatus() { - return upgradeStatus; + if (upgradeStatus.isFreshInstall()) { + charsetChecker.check(ENFORCE_UTF8); + } else if (!upgradeStatus.isUpgraded()) { + charsetChecker.check(); + } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAfterMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCollationDuringMigration.java index ce7c2ea6257..ebb5a5993cb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAfterMigration.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCollationDuringMigration.java @@ -19,23 +19,37 @@ */ package org.sonar.server.db; +import org.picocontainer.Startable; import org.sonar.api.platform.ServerUpgradeStatus; import org.sonar.db.charset.DatabaseCharsetChecker; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; + /** - * Checks charset of all database columns when at least one db migration has been executed. This requires - * to be defined in platform level 3 ({@link org.sonar.server.platform.platformlevel.PlatformLevel3}). + * Checks charset of all database columns when at least one db migration has been executed. */ -public class CheckDatabaseCharsetAfterMigration extends CheckDatabaseCharsetAtStartup { +public class CheckDatabaseCollationDuringMigration implements Startable { + + private final ServerUpgradeStatus upgradeStatus; + private final DatabaseCharsetChecker charsetChecker; - public CheckDatabaseCharsetAfterMigration(ServerUpgradeStatus upgradeStatus, DatabaseCharsetChecker charsetChecker) { - super(upgradeStatus, charsetChecker); + public CheckDatabaseCollationDuringMigration(ServerUpgradeStatus upgradeStatus, DatabaseCharsetChecker charsetChecker) { + this.upgradeStatus = upgradeStatus; + this.charsetChecker = charsetChecker; } @Override public void start() { - if (getUpgradeStatus().isFreshInstall() || getUpgradeStatus().isUpgraded()) { - check(); + if (upgradeStatus.isFreshInstall()) { + charsetChecker.check(ENFORCE_UTF8, AUTO_REPAIR_COLLATION); + } else if (upgradeStatus.isUpgraded()) { + charsetChecker.check(AUTO_REPAIR_COLLATION); } } + + @Override + public void stop() { + // do nothing + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java index 060eae8ee9c..74c62e0d6d1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java @@ -21,7 +21,6 @@ package org.sonar.server.platform.platformlevel; import org.sonar.api.utils.UriReader; import org.sonar.core.util.DefaultHttpDownloader; -import org.sonar.server.db.CheckDatabaseCharsetAfterMigration; import org.sonar.server.platform.PersistentSettings; import org.sonar.server.platform.ServerIdGenerator; import org.sonar.server.platform.ServerIdLoader; @@ -35,7 +34,6 @@ public class PlatformLevel3 extends PlatformLevel { @Override protected void configureLevel() { add( - CheckDatabaseCharsetAfterMigration.class, PersistentSettings.class, ServerMetadataPersister.class, DefaultHttpDownloader.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index 662d46becab..ae8a6c8ee43 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -20,6 +20,7 @@ package org.sonar.server.platform.platformlevel; import org.sonar.server.app.ProcessCommandWrapper; +import org.sonar.server.db.CheckDatabaseCollationDuringMigration; import org.sonar.server.es.IndexerStartupTask; import org.sonar.server.issue.filter.RegisterIssueFilters; import org.sonar.server.platform.ServerLifecycleNotifier; @@ -49,6 +50,7 @@ public class PlatformLevelStartup extends PlatformLevel { @Override protected void configureLevel() { add( + CheckDatabaseCollationDuringMigration.class, IndexerStartupTask.class, RegisterMetrics.class, RegisterQualityGates.class, diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAtStartupTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAtStartupTest.java index 4b2d4dd903b..16d7ec302b8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAtStartupTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAtStartupTest.java @@ -19,14 +19,15 @@ */ package org.sonar.server.db; +import org.junit.After; import org.junit.Test; import org.sonar.api.platform.ServerUpgradeStatus; import org.sonar.db.charset.DatabaseCharsetChecker; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; public class CheckDatabaseCharsetAtStartupTest { @@ -34,23 +35,26 @@ public class CheckDatabaseCharsetAtStartupTest { DatabaseCharsetChecker charsetChecker = mock(DatabaseCharsetChecker.class); CheckDatabaseCharsetAtStartup underTest = new CheckDatabaseCharsetAtStartup(upgradeStatus, charsetChecker); + @After + public void tearDown() { + underTest.stop(); + } + @Test - public void enforces_utf8_if_fresh_install() { + public void enforce_utf8_if_fresh_install() { when(upgradeStatus.isFreshInstall()).thenReturn(true); + underTest.start(); - verify(charsetChecker).check(true); - underTest.stop(); - verifyNoMoreInteractions(charsetChecker); + verify(charsetChecker).check(ENFORCE_UTF8); } @Test - public void does_not_enforce_utf8_if_not_fresh_install() { + public void do_not_enforce_utf8_and_do_not_repair_at_startup_if_not_fresh_install() { when(upgradeStatus.isFreshInstall()).thenReturn(false); + underTest.start(); - verify(charsetChecker).check(false); - underTest.stop(); - verifyNoMoreInteractions(charsetChecker); + verify(charsetChecker).check(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAfterMigrationTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCollationDuringMigrationTest.java index f2f014aedf6..fdb4ef283a8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAfterMigrationTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCollationDuringMigrationTest.java @@ -19,49 +19,54 @@ */ package org.sonar.server.db; +import org.junit.After; import org.junit.Test; import org.sonar.api.platform.ServerUpgradeStatus; import org.sonar.db.charset.DatabaseCharsetChecker; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; -public class CheckDatabaseCharsetAfterMigrationTest { +public class CheckDatabaseCollationDuringMigrationTest { ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class); DatabaseCharsetChecker charsetChecker = mock(DatabaseCharsetChecker.class); - CheckDatabaseCharsetAfterMigration underTest = new CheckDatabaseCharsetAfterMigration(upgradeStatus, charsetChecker); + CheckDatabaseCollationDuringMigration underTest = new CheckDatabaseCollationDuringMigration(upgradeStatus, charsetChecker); + + @After + public void tearDown() { + underTest.stop(); + } @Test - public void enforces_utf8_if_fresh_install() { + public void enforce_utf8_and_optionally_repair_collation_if_fresh_install() { when(upgradeStatus.isFreshInstall()).thenReturn(true); + underTest.start(); - verify(charsetChecker).check(true); - underTest.stop(); - verifyNoMoreInteractions(charsetChecker); + verify(charsetChecker).check(ENFORCE_UTF8, AUTO_REPAIR_COLLATION); } @Test - public void checks_charset_but_does_not_enforce_utf8_if_db_upgrade() { + public void repair_collation_but_do_not_enforce_utf8_if_db_upgrade() { when(upgradeStatus.isFreshInstall()).thenReturn(false); when(upgradeStatus.isUpgraded()).thenReturn(true); + underTest.start(); - verify(charsetChecker).check(false); - underTest.stop(); - verifyNoMoreInteractions(charsetChecker); + verify(charsetChecker).check(AUTO_REPAIR_COLLATION); } @Test - public void does_nothing_if_no_db_changes() { + public void do_nothing_if_no_db_changes() { when(upgradeStatus.isFreshInstall()).thenReturn(false); when(upgradeStatus.isUpgraded()).thenReturn(false); underTest.start(); - underTest.stop(); + verifyZeroInteractions(charsetChecker); } diff --git a/sonar-db/src/main/java/org/sonar/db/charset/CharsetHandler.java b/sonar-db/src/main/java/org/sonar/db/charset/CharsetHandler.java index 233cb14b5a8..df8d2d4fa3c 100644 --- a/sonar-db/src/main/java/org/sonar/db/charset/CharsetHandler.java +++ b/sonar-db/src/main/java/org/sonar/db/charset/CharsetHandler.java @@ -22,6 +22,7 @@ package org.sonar.db.charset; import java.sql.Connection; import java.sql.SQLException; import java.util.List; +import java.util.Set; import javax.annotation.CheckForNull; abstract class CharsetHandler { @@ -34,7 +35,7 @@ abstract class CharsetHandler { this.selectExecutor = selectExecutor; } - abstract void handle(Connection connection, boolean enforceUtf8) throws SQLException; + abstract void handle(Connection connection, Set<DatabaseCharsetChecker.Flag> flags) throws SQLException; protected SqlExecutor getSqlExecutor() { return selectExecutor; @@ -61,5 +62,4 @@ abstract class CharsetHandler { protected final <T> List<T> select(Connection connection, String sql, SqlExecutor.RowConverter<T> rowConverter) throws SQLException { return selectExecutor.executeSelect(connection, sql, rowConverter); } - } diff --git a/sonar-db/src/main/java/org/sonar/db/charset/DatabaseCharsetChecker.java b/sonar-db/src/main/java/org/sonar/db/charset/DatabaseCharsetChecker.java index cdc87bc3b2d..2e89e2d3a97 100644 --- a/sonar-db/src/main/java/org/sonar/db/charset/DatabaseCharsetChecker.java +++ b/sonar-db/src/main/java/org/sonar/db/charset/DatabaseCharsetChecker.java @@ -31,6 +31,9 @@ import org.sonar.db.dialect.MySql; import org.sonar.db.dialect.Oracle; import org.sonar.db.dialect.PostgreSql; +import static com.google.common.collect.Sets.immutableEnumSet; +import static java.util.Arrays.asList; + /** * On fresh installations, checks that all db columns are UTF8. On all installations on MySQL or MSSQL, * whatever fresh or upgrade, fixes case-insensitive columns by converting them to @@ -40,6 +43,10 @@ import org.sonar.db.dialect.PostgreSql; */ public class DatabaseCharsetChecker { + public enum Flag { + ENFORCE_UTF8, AUTO_REPAIR_COLLATION + } + private final Database db; private final SqlExecutor selectExecutor; @@ -53,12 +60,12 @@ public class DatabaseCharsetChecker { this.selectExecutor = selectExecutor; } - public void check(boolean enforceUtf8) { + public void check(Flag... flags) { try { try (Connection connection = db.getDataSource().getConnection()) { CharsetHandler handler = getHandler(db.getDialect()); if (handler != null) { - handler.handle(connection, enforceUtf8); + handler.handle(connection, immutableEnumSet(asList(flags))); } } } catch (SQLException e) { @@ -85,5 +92,4 @@ public class DatabaseCharsetChecker { throw new IllegalArgumentException("Database not supported: " + dialect.getId()); } } - } diff --git a/sonar-db/src/main/java/org/sonar/db/charset/MssqlCharsetHandler.java b/sonar-db/src/main/java/org/sonar/db/charset/MssqlCharsetHandler.java index 774251771eb..cbac5a3d59a 100644 --- a/sonar-db/src/main/java/org/sonar/db/charset/MssqlCharsetHandler.java +++ b/sonar-db/src/main/java/org/sonar/db/charset/MssqlCharsetHandler.java @@ -20,16 +20,21 @@ package org.sonar.db.charset; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import static com.google.common.collect.FluentIterable.from; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.endsWithIgnoreCase; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION; class MssqlCharsetHandler extends CharsetHandler { @@ -40,15 +45,13 @@ class MssqlCharsetHandler extends CharsetHandler { } @Override - void handle(Connection connection, boolean enforceUtf8) throws SQLException { - LOGGER.info("Verify that database collation is case-sensitive and accent-sensitive"); - checkCollation(connection); - } + void handle(Connection connection, Set<DatabaseCharsetChecker.Flag> flags) throws SQLException { + logInit(flags); - private void checkCollation(Connection connection) throws SQLException { // All VARCHAR columns are returned. No need to check database general collation. // Example of row: // issues | kee | Latin1_General_CS_AS + Set<String> errors = new LinkedHashSet<>(); List<ColumnDef> columns = select(connection, ColumnDef.SELECT_COLUMNS + "FROM [INFORMATION_SCHEMA].[COLUMNS] " + @@ -56,9 +59,26 @@ class MssqlCharsetHandler extends CharsetHandler { "ORDER BY table_name,column_name", ColumnDef.ColumnDefRowConverter.INSTANCE); for (ColumnDef column : from(columns).filter(ColumnDef.IsInSonarQubeTablePredicate.INSTANCE)) { if (!endsWithIgnoreCase(column.getCollation(), "_CS_AS")) { - repairColumnCollation(connection, column); + if (flags.contains(AUTO_REPAIR_COLLATION)) { + repairColumnCollation(connection, column); + } else { + errors.add(format("%s.%s", column.getTable(), column.getColumn())); + } } } + + if (!errors.isEmpty()) { + throw MessageException.of(format("Case-sensitive and accent-sensitive collation is required for database columns [%s]", + Joiner.on(", ").join(errors))); + } + } + + private static void logInit(Set<DatabaseCharsetChecker.Flag> flags) { + if (flags.contains(AUTO_REPAIR_COLLATION)) { + LOGGER.info("Repair case-insensitive or accent-insensitive database columns"); + } else { + LOGGER.info("Verify that database columns are case-sensitive and accent-sensitive"); + } } private void repairColumnCollation(Connection connection, ColumnDef column) throws SQLException { diff --git a/sonar-db/src/main/java/org/sonar/db/charset/MysqlCharsetHandler.java b/sonar-db/src/main/java/org/sonar/db/charset/MysqlCharsetHandler.java index 43bec2d9ffc..4f003aba735 100644 --- a/sonar-db/src/main/java/org/sonar/db/charset/MysqlCharsetHandler.java +++ b/sonar-db/src/main/java/org/sonar/db/charset/MysqlCharsetHandler.java @@ -23,8 +23,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Logger; @@ -34,6 +35,8 @@ import static com.google.common.collect.FluentIterable.from; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.containsIgnoreCase; import static org.apache.commons.lang.StringUtils.endsWithIgnoreCase; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; class MysqlCharsetHandler extends CharsetHandler { @@ -45,20 +48,22 @@ class MysqlCharsetHandler extends CharsetHandler { } @Override - void handle(Connection connection, boolean enforceUtf8) throws SQLException { - logInit(enforceUtf8); - checkCollation(connection, enforceUtf8); + void handle(Connection connection, Set<DatabaseCharsetChecker.Flag> flags) throws SQLException { + logInit(flags); + checkCollation(connection, flags); } - private static void logInit(boolean enforceUtf8) { - String message = "Verify that database collation is case-sensitive"; - if (enforceUtf8) { - message = "Verify that database collation is UTF8"; + private static void logInit(Set<DatabaseCharsetChecker.Flag> flags) { + if (flags.contains(AUTO_REPAIR_COLLATION)) { + LOGGER.info("Repair case-insensitive database columns"); + } else if (flags.contains(ENFORCE_UTF8)) { + LOGGER.info("Verify that database collation is UTF8"); + } else { + LOGGER.info("Verify that database collation is case-sensitive"); } - LOGGER.info(message); } - private void checkCollation(Connection connection, boolean enforceUtf8) throws SQLException { + private void checkCollation(Connection connection, Set<DatabaseCharsetChecker.Flag> flags) throws SQLException { // All VARCHAR columns are returned. No need to check database general collation. // Example of row: // issues | kee | utf8 | utf8_bin @@ -66,16 +71,21 @@ class MysqlCharsetHandler extends CharsetHandler { ColumnDef.SELECT_COLUMNS + "FROM INFORMATION_SCHEMA.columns " + "WHERE table_schema=database() and character_set_name is not null and collation_name is not null", ColumnDef.ColumnDefRowConverter.INSTANCE); - List<String> utf8Errors = new ArrayList<>(); + Set<String> errors = new LinkedHashSet<>(); for (ColumnDef column : from(columns).filter(ColumnDef.IsInSonarQubeTablePredicate.INSTANCE)) { - if (enforceUtf8 && !containsIgnoreCase(column.getCharset(), UTF8)) { - utf8Errors.add(format("%s.%s", column.getTable(), column.getColumn())); - } else if (endsWithIgnoreCase(column.getCollation(), "_ci")) { - repairCaseInsensitiveColumn(connection, column); + if (flags.contains(ENFORCE_UTF8) && !containsIgnoreCase(column.getCharset(), UTF8)) { + errors.add(format("%s.%s", column.getTable(), column.getColumn())); + } + if (endsWithIgnoreCase(column.getCollation(), "_ci")) { + if (flags.contains(AUTO_REPAIR_COLLATION)) { + repairCaseInsensitiveColumn(connection, column); + } else { + errors.add(format("%s.%s", column.getTable(), column.getColumn())); + } } } - if (!utf8Errors.isEmpty()) { - throw MessageException.of(format("UTF8 case-sensitive collation is required for database columns [%s]", Joiner.on(", ").join(utf8Errors))); + if (!errors.isEmpty()) { + throw MessageException.of(format("UTF8 case-sensitive collation is required for database columns [%s]", Joiner.on(", ").join(errors))); } } diff --git a/sonar-db/src/main/java/org/sonar/db/charset/OracleCharsetHandler.java b/sonar-db/src/main/java/org/sonar/db/charset/OracleCharsetHandler.java index 179d7c2f796..1ff377175cf 100644 --- a/sonar-db/src/main/java/org/sonar/db/charset/OracleCharsetHandler.java +++ b/sonar-db/src/main/java/org/sonar/db/charset/OracleCharsetHandler.java @@ -21,11 +21,13 @@ package org.sonar.db.charset; import java.sql.Connection; import java.sql.SQLException; +import java.util.Set; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Loggers; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.containsIgnoreCase; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; class OracleCharsetHandler extends CharsetHandler { @@ -34,9 +36,9 @@ class OracleCharsetHandler extends CharsetHandler { } @Override - public void handle(Connection connection, boolean enforceUtf8) throws SQLException { + public void handle(Connection connection, Set<DatabaseCharsetChecker.Flag> flags) throws SQLException { // Oracle does not allow to override character set on tables. Only global charset is verified. - if (enforceUtf8) { + if (flags.contains(ENFORCE_UTF8)) { Loggers.get(getClass()).info("Verify that database charset is UTF8"); checkUtf8(connection); } diff --git a/sonar-db/src/main/java/org/sonar/db/charset/PostgresCharsetHandler.java b/sonar-db/src/main/java/org/sonar/db/charset/PostgresCharsetHandler.java index 1452750ae1f..81cd5bcc1a3 100644 --- a/sonar-db/src/main/java/org/sonar/db/charset/PostgresCharsetHandler.java +++ b/sonar-db/src/main/java/org/sonar/db/charset/PostgresCharsetHandler.java @@ -22,14 +22,16 @@ package org.sonar.db.charset; import com.google.common.base.Joiner; import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Loggers; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.containsIgnoreCase; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; class PostgresCharsetHandler extends CharsetHandler { @@ -38,9 +40,9 @@ class PostgresCharsetHandler extends CharsetHandler { } @Override - void handle(Connection connection, boolean enforceUtf8) throws SQLException { + void handle(Connection connection, Set<DatabaseCharsetChecker.Flag> flags) throws SQLException { // PostgreSQL does not support case-insensitive collations. Only charset must be verified. - if (enforceUtf8) { + if (flags.contains(ENFORCE_UTF8)) { Loggers.get(getClass()).info("Verify that database collation supports UTF8"); checkUtf8(connection); } @@ -58,7 +60,7 @@ class PostgresCharsetHandler extends CharsetHandler { "and udt_name='varchar' " + "order by table_name, column_name", new SqlExecutor.StringsConverter(3 /* columns returned by SELECT */)); boolean mustCheckGlobalCollation = false; - List<String> errors = new ArrayList<>(); + Set<String> errors = new LinkedHashSet<>(); for (String[] row : rows) { if (StringUtils.isBlank(row[2])) { mustCheckGlobalCollation = true; diff --git a/sonar-db/src/test/java/org/sonar/db/charset/DatabaseCharsetCheckerTest.java b/sonar-db/src/test/java/org/sonar/db/charset/DatabaseCharsetCheckerTest.java index 88e47d4af2e..41c87e6b28f 100644 --- a/sonar-db/src/test/java/org/sonar/db/charset/DatabaseCharsetCheckerTest.java +++ b/sonar-db/src/test/java/org/sonar/db/charset/DatabaseCharsetCheckerTest.java @@ -21,6 +21,9 @@ package org.sonar.db.charset; import java.sql.Connection; import java.sql.SQLException; +import java.util.Set; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -35,13 +38,15 @@ import org.sonar.db.dialect.PostgreSql; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.anySet; +import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; public class DatabaseCharsetCheckerTest { @@ -58,8 +63,17 @@ public class DatabaseCharsetCheckerTest { when(underTest.getHandler(dialect)).thenReturn(handler); when(db.getDialect()).thenReturn(dialect); - underTest.check(true); - verify(handler).handle(any(Connection.class), eq(true)); + underTest.check(ENFORCE_UTF8); + verify(handler).handle(any(Connection.class), argThat(new TypeSafeMatcher<Set<DatabaseCharsetChecker.Flag>>() { + @Override + protected boolean matchesSafely(Set<DatabaseCharsetChecker.Flag> flags) { + return flags.contains(ENFORCE_UTF8) && flags.size() == 1; + } + + @Override + public void describeTo(Description description) { + } + })); } @Test @@ -67,11 +81,11 @@ public class DatabaseCharsetCheckerTest { Oracle dialect = new Oracle(); when(underTest.getHandler(dialect)).thenReturn(handler); when(db.getDialect()).thenReturn(dialect); - doThrow(new SQLException("failure")).when(handler).handle(any(Connection.class), anyBoolean()); + doThrow(new SQLException("failure")).when(handler).handle(any(Connection.class), anySet()); expectedException.expect(IllegalStateException.class); expectedException.expectMessage("failure"); - underTest.check(true); + underTest.check(AUTO_REPAIR_COLLATION); } @Test diff --git a/sonar-db/src/test/java/org/sonar/db/charset/MssqlCharsetHandlerTest.java b/sonar-db/src/test/java/org/sonar/db/charset/MssqlCharsetHandlerTest.java index f3748576380..a81bf41ddda 100644 --- a/sonar-db/src/test/java/org/sonar/db/charset/MssqlCharsetHandlerTest.java +++ b/sonar-db/src/test/java/org/sonar/db/charset/MssqlCharsetHandlerTest.java @@ -26,7 +26,9 @@ import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.utils.MessageException; +import static com.google.common.collect.Sets.immutableEnumSet; import static java.util.Arrays.asList; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; @@ -35,6 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION; public class MssqlCharsetHandlerTest { @@ -50,28 +53,42 @@ public class MssqlCharsetHandlerTest { MssqlCharsetHandler underTest = new MssqlCharsetHandler(selectExecutor); @Test - public void does_not_fail_if_charsets_of_all_columns_are_utf8() throws Exception { + public void do_not_fail_if_charsets_of_all_columns_are_CS_AS() throws Exception { answerColumns(asList( new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "Latin1_General", "Latin1_General_CS_AS", "varchar", 10, false), new ColumnDef(TABLE_PROJECTS, COLUMN_NAME, "Latin1_General", "Latin1_General_CS_AS", "varchar", 10, false))); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), Collections.<DatabaseCharsetChecker.Flag>emptySet()); } @Test - public void repairs_case_insensitive_column_without_index() throws Exception { + public void fail_if_a_column_is_case_insensitive_and_repair_is_disabled() throws Exception { answerColumns(asList( new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "Latin1_General", "Latin1_General_CS_AS", "varchar", 10, false), new ColumnDef(TABLE_PROJECTS, COLUMN_NAME, "Latin1_General", "Latin1_General_CI_AI", "varchar", 10, false))); + expectedException.expect(MessageException.class); + expectedException.expectMessage("Case-sensitive and accent-sensitive collation is required for database columns [projects.name]"); Connection connection = mock(Connection.class); - underTest.handle(connection, false); + underTest.handle(connection, Collections.<DatabaseCharsetChecker.Flag>emptySet()); + + verify(selectExecutor, never()).executeUpdate(any(Connection.class), anyString()); + } + + @Test + public void repair_case_insensitive_column_without_index() throws Exception { + answerColumns(asList( + new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "Latin1_General", "Latin1_General_CS_AS", "varchar", 10, false), + new ColumnDef(TABLE_PROJECTS, COLUMN_NAME, "Latin1_General", "Latin1_General_CI_AI", "varchar", 10, false))); + + Connection connection = mock(Connection.class); + underTest.handle(connection, immutableEnumSet(AUTO_REPAIR_COLLATION)); verify(selectExecutor).executeUpdate(connection, "ALTER TABLE projects ALTER COLUMN name varchar(10) COLLATE Latin1_General_CS_AS NOT NULL"); } @Test - public void repairs_case_insensitive_column_with_indices() throws Exception { + public void repair_case_insensitive_column_with_indices() throws Exception { answerColumns(asList( new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "Latin1_General", "Latin1_General_CS_AS", "varchar", 10, false), new ColumnDef(TABLE_PROJECTS, COLUMN_NAME, "Latin1_General", "Latin1_General_CI_AI", "varchar", 10, false))); @@ -81,7 +98,7 @@ public class MssqlCharsetHandlerTest { new MssqlCharsetHandler.ColumnIndex("projects_login_and_name", true, "login,name"))); Connection connection = mock(Connection.class); - underTest.handle(connection, false); + underTest.handle(connection, immutableEnumSet(AUTO_REPAIR_COLLATION)); verify(selectExecutor).executeUpdate(connection, "DROP INDEX projects.projects_name"); verify(selectExecutor).executeUpdate(connection, "DROP INDEX projects.projects_login_and_name"); @@ -97,7 +114,7 @@ public class MssqlCharsetHandlerTest { answerIndices(Collections.<MssqlCharsetHandler.ColumnIndex>emptyList()); Connection connection = mock(Connection.class); - underTest.handle(connection, false); + underTest.handle(connection, immutableEnumSet(AUTO_REPAIR_COLLATION)); verify(selectExecutor).executeUpdate(connection, "ALTER TABLE projects ALTER COLUMN name nvarchar(max) COLLATE Latin1_General_CS_AS NOT NULL"); } @@ -107,7 +124,7 @@ public class MssqlCharsetHandlerTest { answerColumns(asList(new ColumnDef("sys.sysusers", COLUMN_NAME, "Latin1_General", "Latin1_General_CI_AI", "varchar", 10, false))); Connection connection = mock(Connection.class); - underTest.handle(connection, false); + underTest.handle(connection, immutableEnumSet(AUTO_REPAIR_COLLATION)); verify(selectExecutor, never()).executeUpdate(any(Connection.class), anyString()); } diff --git a/sonar-db/src/test/java/org/sonar/db/charset/MysqlCharsetHandlerTest.java b/sonar-db/src/test/java/org/sonar/db/charset/MysqlCharsetHandlerTest.java index 9e9687a2bfd..d050fbedf3c 100644 --- a/sonar-db/src/test/java/org/sonar/db/charset/MysqlCharsetHandlerTest.java +++ b/sonar-db/src/test/java/org/sonar/db/charset/MysqlCharsetHandlerTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.MessageException; +import static com.google.common.collect.Sets.immutableEnumSet; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; @@ -35,6 +36,8 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; public class MysqlCharsetHandlerTest { @@ -50,17 +53,29 @@ public class MysqlCharsetHandlerTest { MysqlCharsetHandler underTest = new MysqlCharsetHandler(selectExecutor); @Test - public void does_not_fail_if_charsets_of_all_columns_are_utf8() throws Exception { + public void do_not_fail_if_charsets_of_all_columns_are_utf8_and_case_sensitive() throws Exception { answerColumnDef(asList( new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "utf8", "utf8_bin", "varchar", 10, false), new ColumnDef(TABLE_PROJECTS, COLUMN_NAME, "utf8", "utf8_bin", "varchar", 10, false))); // all columns are utf8 - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), immutableEnumSet(ENFORCE_UTF8)); } @Test - public void fails_if_not_utf8() throws Exception { + public void fail_if_charsets_of_a_column_is_utf8_but_case_insensitive() throws Exception { + answerColumnDef(asList( + new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "utf8", "utf8_bin", "varchar", 10, false), + new ColumnDef(TABLE_PROJECTS, COLUMN_NAME, "utf8", "utf8_general_ci", "varchar", 10, false))); + + expectedException.expect(MessageException.class); + expectedException.expectMessage("UTF8 case-sensitive collation is required for database columns [projects.name]"); + + underTest.handle(mock(Connection.class), immutableEnumSet(ENFORCE_UTF8)); + } + + @Test + public void fail_if_not_utf8() throws Exception { answerColumnDef(asList( new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "utf8", "utf8_bin", "varchar", 10, false), new ColumnDef(TABLE_PROJECTS, COLUMN_KEE, "latin1", "latin1_german1_ci", "varchar", 10, false), @@ -68,17 +83,17 @@ public class MysqlCharsetHandlerTest { expectedException.expect(MessageException.class); expectedException.expectMessage("UTF8 case-sensitive collation is required for database columns [projects.kee, projects.name]"); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), immutableEnumSet(ENFORCE_UTF8)); } @Test - public void repairs_case_insensitive_column() throws Exception { + public void repair_case_insensitive_column() throws Exception { answerColumnDef(asList( new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "utf8", "utf8_bin", "varchar", 10, false), new ColumnDef(TABLE_PROJECTS, COLUMN_NAME, "latin1", "latin1_swedish_ci", "varchar", 10, false))); Connection connection = mock(Connection.class); - underTest.handle(connection, false); + underTest.handle(connection, immutableEnumSet(AUTO_REPAIR_COLLATION)); verify(selectExecutor).executeUpdate(connection, "ALTER TABLE projects MODIFY name varchar(10) CHARACTER SET 'latin1' COLLATE 'latin1_bin' NOT NULL"); } @@ -88,7 +103,7 @@ public class MysqlCharsetHandlerTest { answerColumnDef(asList(new ColumnDef(TABLE_ISSUES, COLUMN_KEE, "latin1", "latin1_german1_ci", "longtext", 4_294_967_295L, false))); Connection connection = mock(Connection.class); - underTest.handle(connection, false); + underTest.handle(connection, immutableEnumSet(AUTO_REPAIR_COLLATION)); verify(selectExecutor).executeUpdate(connection, "ALTER TABLE " + TABLE_ISSUES + " MODIFY " + COLUMN_KEE + " longtext CHARACTER SET 'latin1' COLLATE 'latin1_bin' NOT NULL"); } diff --git a/sonar-db/src/test/java/org/sonar/db/charset/OracleCharsetHandlerTest.java b/sonar-db/src/test/java/org/sonar/db/charset/OracleCharsetHandlerTest.java index 0d39cdb598d..76be06e7f21 100644 --- a/sonar-db/src/test/java/org/sonar/db/charset/OracleCharsetHandlerTest.java +++ b/sonar-db/src/test/java/org/sonar/db/charset/OracleCharsetHandlerTest.java @@ -23,19 +23,25 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.Collections; import java.util.List; +import java.util.Set; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.MessageException; +import org.sonar.db.charset.DatabaseCharsetChecker.Flag; +import static com.google.common.collect.Sets.immutableEnumSet; import static java.util.Collections.singletonList; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; public class OracleCharsetHandlerTest { + private static final Set<Flag> ENFORCE_UTF8_FLAGS = immutableEnumSet(ENFORCE_UTF8); + @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -47,7 +53,7 @@ public class OracleCharsetHandlerTest { answerSql( singletonList(new String[] {"UTF8"}), singletonList(new String[] {"BINARY"})); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), ENFORCE_UTF8_FLAGS); } @Test @@ -55,7 +61,7 @@ public class OracleCharsetHandlerTest { answerSql( singletonList(new String[] {"AL32UTF8"}), singletonList(new String[] {"BINARY"})); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), ENFORCE_UTF8_FLAGS); } @Test @@ -66,7 +72,7 @@ public class OracleCharsetHandlerTest { expectedException.expect(MessageException.class); expectedException.expectMessage("Oracle must be have UTF8 charset and BINARY sort. NLS_CHARACTERSET is LATIN and NLS_SORT is BINARY."); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), ENFORCE_UTF8_FLAGS); } @Test @@ -77,7 +83,7 @@ public class OracleCharsetHandlerTest { expectedException.expect(MessageException.class); expectedException.expectMessage("Oracle must be have UTF8 charset and BINARY sort. NLS_CHARACTERSET is UTF8 and NLS_SORT is LINGUISTIC."); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), ENFORCE_UTF8_FLAGS); } @Test @@ -86,12 +92,12 @@ public class OracleCharsetHandlerTest { expectedException.expect(MessageException.class); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), ENFORCE_UTF8_FLAGS); } @Test public void does_nothing_if_utf8_must_not_verified() throws Exception { - underTest.handle(mock(Connection.class), false); + underTest.handle(mock(Connection.class), Collections.<Flag>emptySet()); } private void answerSql(List<String[]> firstRequest, List<String[]>... otherRequests) throws SQLException { diff --git a/sonar-db/src/test/java/org/sonar/db/charset/PostgresCharsetHandlerTest.java b/sonar-db/src/test/java/org/sonar/db/charset/PostgresCharsetHandlerTest.java index de30b65f63a..d348e31117e 100644 --- a/sonar-db/src/test/java/org/sonar/db/charset/PostgresCharsetHandlerTest.java +++ b/sonar-db/src/test/java/org/sonar/db/charset/PostgresCharsetHandlerTest.java @@ -22,17 +22,21 @@ package org.sonar.db.charset; import java.sql.Connection; import java.sql.SQLException; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.MessageException; +import org.sonar.db.charset.DatabaseCharsetChecker.Flag; +import static com.google.common.collect.Sets.immutableEnumSet; import static java.util.Arrays.asList; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8; public class PostgresCharsetHandlerTest { @@ -53,7 +57,7 @@ public class PostgresCharsetHandlerTest { new String[] {TABLE_ISSUES, COLUMN_KEE, "utf8"}, new String[] {TABLE_PROJECTS, COLUMN_NAME, "utf8"})); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), immutableEnumSet(ENFORCE_UTF8)); } @Test @@ -68,7 +72,7 @@ public class PostgresCharsetHandlerTest { Arrays.<String[]>asList(new String[] {"utf8"})); // no error - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), immutableEnumSet(ENFORCE_UTF8)); } @Test @@ -81,7 +85,7 @@ public class PostgresCharsetHandlerTest { expectedException.expect(MessageException.class); expectedException.expectMessage("Database columns [projects.kee, projects.name] must support UTF8 collation."); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), immutableEnumSet(ENFORCE_UTF8)); } @Test @@ -98,12 +102,12 @@ public class PostgresCharsetHandlerTest { expectedException.expect(MessageException.class); expectedException.expectMessage("Database collation is latin. It must support UTF8."); - underTest.handle(mock(Connection.class), true); + underTest.handle(mock(Connection.class), immutableEnumSet(ENFORCE_UTF8)); } @Test public void does_nothing_if_utf8_must_not_verified() throws Exception { - underTest.handle(mock(Connection.class), false); + underTest.handle(mock(Connection.class), Collections.<Flag>emptySet()); } private void answerSql(List<String[]> firstRequest, List<String[]>... otherRequests) throws SQLException { |