diff options
7 files changed, 224 insertions, 2 deletions
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplIT.java index 187222cfe57..5e14bdf0d7f 100644 --- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplIT.java +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplIT.java @@ -73,6 +73,18 @@ class MigrationHistoryImplIT { assertThat(underTest.getLastMigrationNumber()).contains(12L); } + @Test + void getInitialDbVersion_shouldReturnVersionAtStartUp() throws SQLException { + underTest.start(); + assertThat(underTest.getInitialDbVersion()).isEqualTo(-1); + + insert(12, 5, 30, 8); + underTest.start(); + insert(35,37,42); + + assertThat(underTest.getInitialDbVersion()).isEqualTo(30); + } + private void insert(int... versions) throws SQLException { try (Connection connection = dbTester.database().getDataSource().getConnection()) { Arrays.stream(versions).forEach(version -> insert(connection, version)); diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/EnableSpecificMqrModeIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/EnableSpecificMqrModeIT.java new file mode 100644 index 00000000000..f51870d849f --- /dev/null +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/EnableSpecificMqrModeIT.java @@ -0,0 +1,117 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.db.migration.version.v108; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.api.utils.System2; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.MigrationDbTester; +import org.sonar.server.platform.db.migration.history.MigrationHistory; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.core.config.MQRModeConstants.MULTI_QUALITY_MODE_ENABLED; + +class EnableSpecificMqrModeIT { + + @RegisterExtension + public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(EnableSpecificMqrMode.class); + + private final UuidFactory uuidFactory = new SequenceUuidFactory(); + private final MigrationHistory migrationHistory = mock(MigrationHistory.class); + private final System2 system2 = new TestSystem2().setNow(2_000_000_000L); + + private final DataChange underTest = new EnableSpecificMqrMode(db.database(), migrationHistory, uuidFactory, system2); + + @ParameterizedTest + @MethodSource("versions") + void execute_correctlyInsertsProperty(long version, String expectedResult) throws SQLException { + when(migrationHistory.getInitialDbVersion()).thenReturn(version); + underTest.execute(); + + assertThat(getPropertyFromDB()).hasSize(1); + assertThat(getPropertyFromDB().get(0)) + .containsEntry("text_value", expectedResult) + .containsEntry("is_empty", false) + .containsEntry("created_at", 2_000_000_000L) + .containsEntry("uuid", "00000000-0000-0000-0000-000000000001"); + } + + @ParameterizedTest + @MethodSource("versions") + void execute_doesNothingIfPropertyAlreadyExists(long version) throws SQLException { + when(migrationHistory.getInitialDbVersion()).thenReturn(version); + + String uuid = uuidFactory.create(); + db.executeInsert("properties", + "prop_key", MULTI_QUALITY_MODE_ENABLED, + "text_value", "false", + "is_empty", "false", + "created_at", 1_000_000_000L, + "uuid", uuid + ); + underTest.execute(); + + assertThat(getPropertyFromDB()).hasSize(1); + assertThat(getPropertyFromDB().get(0)) + .containsEntry("text_value", "false") + .containsEntry("is_empty", false) + .containsEntry("created_at", 1_000_000_000L) + .containsEntry("uuid", uuid); + } + + @Test + void execute_shouldBeReentrant() throws SQLException { + when(migrationHistory.getInitialDbVersion()).thenReturn(102_000L); + underTest.execute(); + underTest.execute(); + + assertThat(getPropertyFromDB()).hasSize(1); + assertThat(getPropertyFromDB().get(0)) + .containsEntry("text_value", "true") + .containsEntry("is_empty", false) + .containsEntry("created_at", 2_000_000_000L) + .containsEntry("uuid", "00000000-0000-0000-0000-000000000001"); + } + + private List<Map<String, Object>> getPropertyFromDB() { + String sql = "SELECT text_value, is_empty, created_at, uuid FROM properties WHERE prop_key = '" + MULTI_QUALITY_MODE_ENABLED + "'"; + return db.select(sql); + } + + private static Stream<Arguments> versions() { + return Stream.of( + Arguments.of(102_000L, "true"), + Arguments.of(-1L, "true"), + Arguments.of(101_990L, "false")); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java index c771c6c79d2..7e726181a8b 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java @@ -53,4 +53,10 @@ public interface MigrationHistory extends Startable { */ void done(RegisteredMigrationStep dbMigration); + /** + * Returns the initial version of the database. + * + * @return a long >= 0 or -1 if the migration history is empty. + */ + long getInitialDbVersion(); } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java index 49ebd5166ed..296a4f1da9e 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java @@ -41,6 +41,8 @@ public class MigrationHistoryImpl implements MigrationHistory { private final Database database; private final MigrationHistoryMeddler migrationHistoryMeddler; + private long initialDbVersion; + public MigrationHistoryImpl(Database database, MigrationHistoryMeddler migrationHistoryMeddler) { this.database = database; this.migrationHistoryMeddler = migrationHistoryMeddler; @@ -50,6 +52,7 @@ public class MigrationHistoryImpl implements MigrationHistory { public void start() { try (Connection connection = database.getDataSource().getConnection()) { checkState(DatabaseUtils.tableExists(MigrationHistoryTable.NAME, connection), "Migration history table is missing"); + this.initialDbVersion = getLastMigrationNumber().orElse(-1L); migrationHistoryMeddler.meddle(this); } catch (SQLException e) { Throwables.propagate(e); @@ -91,6 +94,11 @@ public class MigrationHistoryImpl implements MigrationHistory { } } + @Override + public long getInitialDbVersion() { + return initialDbVersion; + } + private static List<Long> selectVersions(Connection connection) throws SQLException { try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("select version from " + SCHEMA_MIGRATIONS_TABLE)) { diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java index 38b61ec6b3f..1cd9f0af8c8 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v00/PopulateInitialSchema.java @@ -256,7 +256,7 @@ public class PopulateInitialSchema extends DataChange { context.prepareUpsert("truncate table " + table).execute().commit(); } - private static String createInsertStatement(String tableName, String firstColumn, String... otherColumns) { + public static String createInsertStatement(String tableName, String firstColumn, String... otherColumns) { return "insert into " + tableName + " " + "(" + concat(of(firstColumn), stream(otherColumns)).collect(joining(",")) + ")" + " values" + diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/DbVersion108.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/DbVersion108.java index aab86608e2f..412306bceb7 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/DbVersion108.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/DbVersion108.java @@ -56,7 +56,8 @@ public class DbVersion108 implements DbVersion { .add(10_8_013, "Drop index on 'project_branches.measures_migrated'", DropIndexOnProjectBranchesMeasuresMigrated.class) .add(10_8_014, "Drop 'measures_migrated' column on 'project_branches' table", DropMeasuresMigratedColumnInProjectBranchesTable.class) .add(10_8_015, "Add column 'impacts' in 'active_rules' table", AddImpactsColumnInActiveRulesTable.class) - .add(10_8_016, "Create 'project_dependencies' table", CreateProjectDependenciesTable.class); + .add(10_8_016, "Create 'project_dependencies' table", CreateProjectDependenciesTable.class) + .add(10_8_017, "Enable specific MQR mode", EnableSpecificMqrMode.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/EnableSpecificMqrMode.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/EnableSpecificMqrMode.java new file mode 100644 index 00000000000..bdbcc0a43ea --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/EnableSpecificMqrMode.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.db.migration.version.v108; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.history.MigrationHistory; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.Upsert; + +import static org.sonar.core.config.MQRModeConstants.MULTI_QUALITY_MODE_ENABLED; +import static org.sonar.server.platform.db.migration.version.v00.PopulateInitialSchema.createInsertStatement; + +public class EnableSpecificMqrMode extends DataChange { + private final MigrationHistory migrationHistory; + private final UuidFactory uuidFactory; + private final System2 system2; + + public EnableSpecificMqrMode(Database db, MigrationHistory migrationHistory, UuidFactory uuidFactory, System2 system2) { + super(db); + this.migrationHistory = migrationHistory; + this.uuidFactory = uuidFactory; + this.system2 = system2; + } + + @Override + public void execute(Context context) throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + if (!paramExists(connection)) { + long version = migrationHistory.getInitialDbVersion(); + boolean mqrModeEnabled = version >= 102_000L || version == -1L; + Upsert upsert = context.prepareUpsert( + createInsertStatement("properties", + "uuid", + "prop_key", + "is_empty", + "text_value", + "created_at")); + upsert.setString(1, uuidFactory.create()) + .setString(2, MULTI_QUALITY_MODE_ENABLED) + .setBoolean(3, false) + .setString(4, String.valueOf(mqrModeEnabled)) + .setLong(5, system2.now()); + upsert.execute().commit(); + } + } + } + + private static boolean paramExists(Connection connection) throws SQLException { + String sql = "SELECT count(1) FROM properties WHERE prop_key = '" + MULTI_QUALITY_MODE_ENABLED + "'"; + try (PreparedStatement statement = connection.prepareStatement(sql)) { + ResultSet result = statement.executeQuery(); + return result.next() && result.getInt(1) > 0; + } + } +} |