From 3ffbe482c4ab61bbf9c4eae3b656244961152e3d Mon Sep 17 00:00:00 2001 From: Dejan Milisavljevic Date: Thu, 31 Oct 2024 17:32:30 +0100 Subject: [PATCH] SONAR-23998 Migrate project_measures deprecated metrics MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Co-authored-by: Léo Geoffroy --- .../CreateNewSoftwareQualityMetricsIT.java | 83 +++++++++ ...eProjectMeasuresDeprecatedMetricsTest.java | 175 ++++++++++++++++++ ...AbstractMigrateLiveMeasuresToMeasures.java | 2 +- .../v108/CreateNewSoftwareQualityMetrics.java | 78 ++++++++ .../migration/version/v108/DbVersion108.java | 4 +- .../version/v108/MeasureMigration.java | 2 +- ...grateProjectMeasuresDeprecatedMetrics.java | 108 +++++++++++ 7 files changed, 449 insertions(+), 3 deletions(-) create mode 100644 server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetricsIT.java create mode 100644 server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetricsTest.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetrics.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetrics.java diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetricsIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetricsIT.java new file mode 100644 index 00000000000..16421382ed3 --- /dev/null +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetricsIT.java @@ -0,0 +1,83 @@ +/* + * 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.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonar.core.util.UuidFactoryImpl; +import org.sonar.db.MigrationDbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +class CreateNewSoftwareQualityMetricsIT { + @RegisterExtension + public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(CreateNewSoftwareQualityMetrics.class); + private final CreateNewSoftwareQualityMetrics underTest = new CreateNewSoftwareQualityMetrics(db.database(), UuidFactoryImpl.INSTANCE); + + @Test + void execute_shouldCreateMetrics() throws SQLException { + assertThat(db.select("select name,direction,qualitative,enabled,best_value,optimized_best_value,delete_historical_data from metrics")) + .isEmpty(); + underTest.execute(); + assertThat(db.select("select name,direction,qualitative,enabled,best_value,optimized_best_value,delete_historical_data from metrics")) + .hasSize(6) + .extracting(s -> s.get("name"), s -> s.get("direction"), s -> s.get("qualitative"), s -> s.get("enabled"), s -> s.get("best_value"), s -> s.get("optimized_best_value"), + s -> s.get("delete_historical_data")) + .containsExactlyInAnyOrder( + tuple("software_quality_reliability_issues", -1L, false, true, 0.0, true, false), + tuple("software_quality_security_issues", -1L, false, true, 0.0, true, false), + tuple("new_software_quality_reliability_issues", -1L, true, true, 0.0, true, true), + tuple("new_software_quality_security_issues", -1L, true, true, 0.0, true, true), + tuple("new_software_quality_maintainability_issues", -1L, true, true, 0.0, true, true), + tuple("software_quality_maintainability_issues", -1L, false, true, 0.0, true, false)); + } + + @Test + void execute_shouldBeReentrant() throws SQLException { + underTest.execute(); + underTest.execute(); + assertThat(db.select("select name,direction,qualitative,enabled,best_value,optimized_best_value,delete_historical_data from metrics")) + .hasSize(6); + } + + @Test + void execute_whenOnlyOneMetricExists_shouldCreateOtherOnes() throws SQLException { + String existingMetricUuid = insertMetric("software_quality_security_issues"); + underTest.execute(); + + assertThat(db.select("select uuid from metrics")) + .hasSize(6) + .extracting(e -> e.get("uuid")) + .contains(existingMetricUuid); + } + + private String insertMetric(String key) { + String uuid = UuidFactoryImpl.INSTANCE.create(); + Map map = Map.ofEntries( + Map.entry("UUID", uuid), + Map.entry("NAME", key)); + db.executeInsert("metrics", map); + return uuid; + } + +} diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetricsTest.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetricsTest.java new file mode 100644 index 00000000000..6eba63a3096 --- /dev/null +++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetricsTest.java @@ -0,0 +1,175 @@ +/* + * 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.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonar.core.util.UuidFactoryImpl; +import org.sonar.db.MigrationDbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.measures.CoreMetrics.MAINTAINABILITY_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.RELIABILITY_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_ISSUES_KEY; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY; +import static org.sonar.server.platform.db.migration.version.v108.MeasureMigration.MIGRATION_MAP; + +class MigrateProjectMeasuresDeprecatedMetricsTest { + private static final String ANALYSIS_UUID_1 = UuidFactoryImpl.INSTANCE.create(); + private static final String ANALYSIS_UUID_2 = UuidFactoryImpl.INSTANCE.create(); + @RegisterExtension + private final MigrationDbTester db = MigrationDbTester.createForMigrationStep(MigrateProjectMeasuresDeprecatedMetrics.class); + private final MigrateProjectMeasuresDeprecatedMetrics underTest = new MigrateProjectMeasuresDeprecatedMetrics(db.database(), + UuidFactoryImpl.INSTANCE); + private Map metricsToMigrate; + private Map replacementMetrics; + + @BeforeEach + void init() { + metricsToMigrate = insertMetricsToMigrate(); + replacementMetrics = insertReplacementMetrics(); + } + + @Test + void execute_shouldCreateNewMetrics() throws SQLException { + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "1"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(RELIABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "3"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(SECURITY_ISSUES_KEY), ANALYSIS_UUID_1, "5"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(NEW_MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "11"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(NEW_RELIABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "13"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(NEW_SECURITY_ISSUES_KEY), ANALYSIS_UUID_1, "15"); + underTest.execute(); + + assertThat(db.select("select metric_uuid, value from project_measures where metric_uuid in (%s)" + .formatted(replacementMetrics.values().stream().map(s -> "'" + s + "'").collect(Collectors.joining(","))))) + .hasSize(6) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY), "value", 1.0))) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY), "value", 3.0))) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_SECURITY_ISSUES_KEY), "value", 5.0))) + .contains((Map.of("metric_uuid", replacementMetrics.get(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY), "value", 11.0))) + .contains((Map.of("metric_uuid", replacementMetrics.get(NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY), "value", 13.0))) + .contains((Map.of("metric_uuid", replacementMetrics.get(NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY), "value", 15.0))); + } + + @Test + void execute_shouldBeReentrant() throws SQLException { + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "1"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(RELIABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "3"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(SECURITY_ISSUES_KEY), ANALYSIS_UUID_1, "5"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(NEW_MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "11"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(NEW_RELIABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "13"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(NEW_SECURITY_ISSUES_KEY), ANALYSIS_UUID_1, "15"); + underTest.execute(); + underTest.execute(); + assertThat(db.select("select * from project_measures")) + .hasSize(MIGRATION_MAP.size() * 2); + } + + @Test + void execute_whenValueCannotBeConverted_shouldCreateOtherNewMetrics() throws SQLException { + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "1"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(RELIABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "NOT_VALID_NUMBER"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(SECURITY_ISSUES_KEY), ANALYSIS_UUID_1, "5"); + underTest.execute(); + + assertThat(db.select("select metric_uuid, value from project_measures where metric_uuid in (%s)" + .formatted(replacementMetrics.values().stream().map(s -> "'" + s + "'").collect(Collectors.joining(","))))) + .hasSize(2) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY), "value", 1.0))) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_SECURITY_ISSUES_KEY), "value", 5.0))); + } + + @Test + void execute_whenWasPartiallyMigrated_shouldContinueWithOtherAnalysis() throws SQLException { + + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_1, "1"); + createProjectMeasureForNewMetric(replacementMetrics.get(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_1, 1); + + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(MAINTAINABILITY_ISSUES_KEY), ANALYSIS_UUID_2, "4"); + createProjectMeasureForDeprecatedMetric(metricsToMigrate.get(SECURITY_ISSUES_KEY), ANALYSIS_UUID_2, "5"); + underTest.execute(); + + assertThat(db.select("select metric_uuid, value, analysis_uuid from project_measures where metric_uuid in (%s)" + .formatted(replacementMetrics.values().stream().map(s -> "'" + s + "'").collect(Collectors.joining(","))))) + .hasSize(3) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY), "value", 1.0, "analysis_uuid" + , ANALYSIS_UUID_1))) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY), "value", 4.0, "analysis_uuid" + , ANALYSIS_UUID_2))) + .contains((Map.of("metric_uuid", replacementMetrics.get(SOFTWARE_QUALITY_SECURITY_ISSUES_KEY), "value", 5.0, "analysis_uuid", + ANALYSIS_UUID_2))); + } + + private void createProjectMeasureForDeprecatedMetric(String metricUuid, String analysisUuid, String totalIssues) { + String uuid = UuidFactoryImpl.INSTANCE.create(); + Map map = Map.ofEntries( + Map.entry("UUID", uuid), + Map.entry("TEXT_VALUE", "{\"LOW\":X,\"MEDIUM\":Y,\"HIGH\":Z,\"total\":" + totalIssues + "}"), + Map.entry("ANALYSIS_UUID", analysisUuid), + Map.entry("METRIC_UUID", metricUuid), + Map.entry("COMPONENT_UUID", UuidFactoryImpl.INSTANCE.create())); + db.executeInsert("project_measures", map); + } + + private void createProjectMeasureForNewMetric(String metricUuid, String analysisUuid, int totalIssues) { + String uuid = UuidFactoryImpl.INSTANCE.create(); + Map map = Map.ofEntries( + Map.entry("UUID", uuid), + Map.entry("VALUE", totalIssues), + Map.entry("ANALYSIS_UUID", analysisUuid), + Map.entry("METRIC_UUID", metricUuid), + Map.entry("COMPONENT_UUID", UuidFactoryImpl.INSTANCE.create())); + db.executeInsert("project_measures", map); + } + + private Map insertMetricsToMigrate() { + Map createdMetrics = new HashMap<>(); + MIGRATION_MAP.keySet().forEach(metricKey -> createdMetrics.put(metricKey, insertMetric(metricKey))); + return createdMetrics; + } + + private Map insertReplacementMetrics() { + Map createdMetrics = new HashMap<>(); + MIGRATION_MAP.values().forEach(metricKey -> createdMetrics.put(metricKey, insertMetric(metricKey))); + return createdMetrics; + } + + private String insertMetric(String key) { + String uuid = UuidFactoryImpl.INSTANCE.create(); + Map map = Map.ofEntries( + Map.entry("UUID", uuid), + Map.entry("NAME", key)); + db.executeInsert("metrics", map); + return uuid; + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java index 3421bd0b977..8702275b77c 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java @@ -217,7 +217,7 @@ public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange { String migratedMetricKey = MeasureMigration.getMigrationMetricKey(metricName); if (migratedMetricKey != null) { try { - Object migratedValue = MeasureMigration.migrate(metricValue); + Long migratedValue = MeasureMigration.migrate(metricValue); if (migratedValue != null) { measureValues.put(migratedMetricKey, migratedValue); } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetrics.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetrics.java new file mode 100644 index 00000000000..11a6c913776 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetrics.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.core.util.UuidFactory; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.Upsert; +import org.sonar.server.platform.db.migration.version.v00.PopulateInitialSchema; + +public class CreateNewSoftwareQualityMetrics extends DataChange { + private final UuidFactory uuidFactory; + + public CreateNewSoftwareQualityMetrics(Database db, UuidFactory uuidFactory) { + super(db); + this.uuidFactory = uuidFactory; + } + + @Override + public void execute(Context context) throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + for (String metric : MeasureMigration.MIGRATION_MAP.values()) { + if (!metricExists(connection, metric)) { + Upsert upsert = context.prepareUpsert(PopulateInitialSchema.createInsertStatement("metrics", + "name", + "direction", + "qualitative", + "enabled", + "best_value", + "optimized_best_value", + "delete_historical_data", + "uuid" + )); + upsert + .setString(1, metric) + .setInt(2, -1) + .setBoolean(3, metric.startsWith("new_")) + .setBoolean(4, true) + .setDouble(5, 0.0) + .setBoolean(6, true) + .setBoolean(7, metric.startsWith("new_")) + .setString(8, uuidFactory.create()); + upsert.execute().commit(); + } + } + } + } + + private static boolean metricExists(Connection connection, String metric) throws SQLException { + String sql = "SELECT count(1) FROM metrics WHERE name = ?"; + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, metric); + ResultSet result = statement.executeQuery(); + return result.next() && result.getInt(1) > 0; + } + } +} 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 04f4dd3cc63..20686209202 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 @@ -59,7 +59,9 @@ public class DbVersion108 implements DbVersion { .add(10_8_016, "Create 'project_dependencies' table", CreateProjectDependenciesTable.class) .add(10_8_017, "Enable specific MQR mode", EnableSpecificMqrMode.class) .add(10_8_018, "Make columns 'published_at' and 'last_modified_at' nullable on the 'cves' table", AlterCveColumnsToNullable.class) - .add(10_8_019, "Delete Software Quality ratings from project_measures", DeleteSoftwareQualityRatingFromProjectMeasures.class); + .add(10_8_019, "Delete Software Quality ratings from project_measures", DeleteSoftwareQualityRatingFromProjectMeasures.class) + .add(10_8_020, "Create new software quality metrics", CreateNewSoftwareQualityMetrics.class) + .add(10_8_021, "Migrate deprecated project_measures to replacement metrics", MigrateProjectMeasuresDeprecatedMetrics.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MeasureMigration.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MeasureMigration.java index 1ab0ec020b9..3335c93f85c 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MeasureMigration.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MeasureMigration.java @@ -48,7 +48,7 @@ public class MeasureMigration { } @CheckForNull - public static Object migrate(Object value) { + public static Long migrate(Object value) { Matcher matcher = VALUE_EXTRACTION_PATTERN.matcher(value.toString()); if (matcher.find()) { return Long.valueOf(matcher.group(1)); diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetrics.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetrics.java new file mode 100644 index 00000000000..5b98e4350c9 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetrics.java @@ -0,0 +1,108 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class MigrateProjectMeasuresDeprecatedMetrics extends DataChange { + + private static final String SELECT_QUERY = """ + select m.name, pm.component_uuid , pm.analysis_uuid, pm.text_value + from project_measures pm + join metrics m + on pm.metric_uuid = m.uuid + and m.name in (%s) + and not exists ( + select 1 + from project_measures pm2 + join metrics m2 + on pm2.metric_uuid = m2.uuid + and pm.analysis_uuid = pm2.analysis_uuid + and m2.name in ('software_quality_maintainability_issues') + ) + order by pm.analysis_uuid + """.formatted(MeasureMigration.MIGRATION_MAP.keySet().stream().map(s -> "'" + s + "'").collect(Collectors.joining(","))); + + private static final String SELECT_NEW_METRICS_UUID = """ + select m.name, m.uuid + from metrics m + where m.name in (%s) + """.formatted(MeasureMigration.MIGRATION_MAP.values().stream().map(s -> "'" + s + "'").collect(Collectors.joining(","))); + + private final UuidFactory uuidFactory; + + public MigrateProjectMeasuresDeprecatedMetrics(Database db, UuidFactory uuidFactory) { + super(db); + this.uuidFactory = uuidFactory; + } + + @Override + protected void execute(Context context) throws SQLException { + + Map newMetricsUuid = getNewMetricsUuid(); + + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select(SELECT_QUERY); + massUpdate.update("INSERT INTO project_measures (value, component_uuid, analysis_uuid, uuid, metric_uuid) VALUES (?, ?, ?, ?, ?)"); + + massUpdate.execute((row, update, index) -> { + String metricName = row.getString(1); + String componentUuid = row.getString(2); + String analysisUuid = row.getString(3); + String textValue = row.getString(4); + + Long migratedValue = MeasureMigration.migrate(textValue); + if (migratedValue != null) { + update.setDouble(1, migratedValue.doubleValue()); + update.setString(2, componentUuid); + update.setString(3, analysisUuid); + update.setString(4, uuidFactory.create()); + String newMetricName = MeasureMigration.MIGRATION_MAP.get(metricName); + update.setString(5, newMetricsUuid.get(newMetricName)); + return true; + } else { + return false; + } + }); + } + + private Map getNewMetricsUuid() throws SQLException{ + Map map = new HashMap<>(); + try (Connection connection = getDatabase().getDataSource().getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(SELECT_NEW_METRICS_UUID)) { + ResultSet result = statement.executeQuery(); + while (result.next()) { + map.put(result.getString(1), result.getString(2)); + } + return map; + } + } + } +} -- 2.39.5