]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23998 Migrate project_measures deprecated metrics
authorDejan Milisavljevic <dejan.milisavljevic@sonarsource.com>
Thu, 31 Oct 2024 16:32:30 +0000 (17:32 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 5 Nov 2024 20:03:02 +0000 (20:03 +0000)
Co-authored-by: Léo Geoffroy <leo.geoffroy@sonarsource.com>
server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetricsIT.java [new file with mode: 0644]
server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetricsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/AbstractMigrateLiveMeasuresToMeasures.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/CreateNewSoftwareQualityMetrics.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/DbVersion108.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MeasureMigration.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v108/MigrateProjectMeasuresDeprecatedMetrics.java [new file with mode: 0644]

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 (file)
index 0000000..1642138
--- /dev/null
@@ -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<String, Object> 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 (file)
index 0000000..6eba63a
--- /dev/null
@@ -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<String, String> metricsToMigrate;
+  private Map<String, String> 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<String, Object> 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<String, Object> 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<String, String> insertMetricsToMigrate() {
+    Map<String, String> createdMetrics = new HashMap<>();
+    MIGRATION_MAP.keySet().forEach(metricKey -> createdMetrics.put(metricKey, insertMetric(metricKey)));
+    return createdMetrics;
+  }
+
+  private Map<String, String> insertReplacementMetrics() {
+    Map<String, String> 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<String, Object> map = Map.ofEntries(
+      Map.entry("UUID", uuid),
+      Map.entry("NAME", key));
+    db.executeInsert("metrics", map);
+    return uuid;
+  }
+}
index 3421bd0b977b5a4561e20c34d2fb2349d6c2b34d..8702275b77ceca1edd57edc68d2614e4ba66e512 100644 (file)
@@ -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 (file)
index 0000000..11a6c91
--- /dev/null
@@ -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;
+    }
+  }
+}
index 04f4dd3cc633409df78c5435739b080f4bb91ab2..20686209202ae5248a4e63a09af7f1f3a66c3693 100644 (file)
@@ -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);
   }
 
 }
index 1ab0ec020b9ad0cd6edf5b01f965036f2a04688c..3335c93f85cd638414636969a61b688dbb9e6619 100644 (file)
@@ -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 (file)
index 0000000..5b98e43
--- /dev/null
@@ -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<String, String> 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<String, String> getNewMetricsUuid() throws SQLException{
+    Map<String, String> 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;
+      }
+    }
+  }
+}