]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23213 Measures double write - purge
authorEric Giffon <eric.giffon@sonarsource.com>
Thu, 3 Oct 2024 09:22:53 +0000 (11:22 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 14 Oct 2024 20:03:02 +0000 (20:03 +0000)
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/db/ReadOnlyPropertiesDao.java [deleted file]
server/sonar-ce/src/main/java/org/sonar/ce/db/package-info.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/db/ReadOnlyPropertiesDaoTest.java [deleted file]
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoWithAuditTest.java
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/measure/MeasureDbTester.java

index e368e2ed1376c8b665beed9c62b160293667dd11..1572c0a6257f7641fa89c1b8bf97e467ab97ceae 100644 (file)
@@ -48,7 +48,6 @@ import org.sonar.ce.StandaloneCeDistributedInformation;
 import org.sonar.ce.analysis.cache.cleaning.AnalysisCacheCleaningModule;
 import org.sonar.ce.async.SynchronousAsyncExecution;
 import org.sonar.ce.cleaning.CeCleaningModule;
-import org.sonar.ce.db.ReadOnlyPropertiesDao;
 import org.sonar.ce.issue.index.NoAsyncIssueIndexing;
 import org.sonar.ce.logging.CeProcessLogging;
 import org.sonar.ce.monitoring.CEQueueStatusImpl;
@@ -296,7 +295,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
 
       // DB
       new DaoModule(),
-      ReadOnlyPropertiesDao.class,
       DBSessionsImpl.class,
       DbClient.class,
 
@@ -326,9 +324,6 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
       DatabaseSettingsEnabler.class,
       UrlSettings.class,
 
-      // add ReadOnlyPropertiesDao at level2 again so that it shadows PropertiesDao
-      ReadOnlyPropertiesDao.class,
-
       // plugins
       PluginClassloaderFactory.class,
       CePluginJarExploder.class,
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/db/ReadOnlyPropertiesDao.java b/server/sonar-ce/src/main/java/org/sonar/ce/db/ReadOnlyPropertiesDao.java
deleted file mode 100644 (file)
index 77df35e..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.ce.db;
-
-import java.util.Map;
-import javax.annotation.Nullable;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbSession;
-import org.sonar.db.MyBatis;
-import org.sonar.db.audit.NoOpAuditPersister;
-import org.sonar.db.property.PropertiesDao;
-import org.sonar.db.property.PropertyDto;
-
-/**
- * Compute Engine specific override of {@link PropertiesDao} and {@link org.sonar.db.property.PropertiesDao} which
- * implements no write method (ie. insert/update/delete) because updating the Properties is the Web Server responsibility
- * alone.
- * <p>
- * This ugly trick is required because licensed plugin bundle {@link com.sonarsource.license.api.internal.ServerLicenseVerifierImpl}
- * which update license properties by calling {@link PropertiesDao} directly and this can not be disabled.
- * </p>
- */
-public class ReadOnlyPropertiesDao extends PropertiesDao {
-  public ReadOnlyPropertiesDao(MyBatis mybatis, System2 system2, UuidFactory uuidFactory) {
-    super(mybatis, system2, uuidFactory, new NoOpAuditPersister());
-  }
-
-  @Override
-  public void saveProperty(DbSession session, PropertyDto property, @Nullable String userLogin,
-    @Nullable String projectKey, @Nullable String projectName, @Nullable String qualifier) {
-    // do nothing
-  }
-
-  @Override
-  public void saveProperty(PropertyDto property) {
-    // do nothing
-  }
-
-  @Override
-  public void deleteProjectProperty(String key, String projectUuid, String projectKey, String projectName, String qualifier) {
-    // do nothing
-  }
-
-  @Override
-  public void deleteProjectProperty(DbSession session, String key, String projectUuid, String projectKey,
-    String projectName, String qualifier) {
-    // do nothing
-  }
-
-  @Override
-  public void deleteProjectProperties(String key, String value, DbSession session) {
-    // do nothing
-  }
-
-  @Override
-  public void deleteProjectProperties(String key, String value) {
-    // do nothing
-  }
-
-  @Override
-  public void deleteGlobalProperty(String key, DbSession session) {
-    // do nothing
-  }
-
-  @Override
-  public void deleteGlobalProperty(String key) {
-    // do nothing
-  }
-
-  @Override
-  public void saveGlobalProperties(Map<String, String> properties) {
-    // do nothing
-  }
-
-  @Override
-  public void renamePropertyKey(String oldKey, String newKey) {
-    // do nothing
-  }
-
-}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/db/package-info.java b/server/sonar-ce/src/main/java/org/sonar/ce/db/package-info.java
deleted file mode 100644 (file)
index 4f8609c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.ce.db;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/db/ReadOnlyPropertiesDaoTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/db/ReadOnlyPropertiesDaoTest.java
deleted file mode 100644 (file)
index 7e55ca7..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.ce.db;
-
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbSession;
-import org.sonar.db.MyBatis;
-import org.sonar.db.property.PropertyDto;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-public class ReadOnlyPropertiesDaoTest {
-  private MyBatis myBatis = mock(MyBatis.class);
-  private DbSession dbSession = mock(DbSession.class);
-  private PropertyDto propertyDto = mock(PropertyDto.class);
-  private PropertyDto oldPropertyDto = mock(PropertyDto.class);
-  private UuidFactory uuidFactory = new SequenceUuidFactory();
-  private ReadOnlyPropertiesDao underTest = new ReadOnlyPropertiesDao(myBatis, System2.INSTANCE, uuidFactory);
-
-  @Test
-  public void insertProperty() {
-    underTest.saveProperty(dbSession, propertyDto, null, null, null, null);
-
-    assertNoInteraction();
-  }
-
-  @Test
-  public void insertProperty1() {
-    underTest.saveProperty(propertyDto);
-
-    assertNoInteraction();
-  }
-
-  @Test
-  public void deleteProjectProperty() {
-    underTest.deleteProjectProperty(null, null, null, null, null, null);
-
-    assertNoInteraction();
-
-  }
-
-  @Test
-  public void deleteProjectProperty1() {
-    underTest.deleteProjectProperty(dbSession, null, null, null, null, null);
-
-    assertNoInteraction();
-
-  }
-
-  @Test
-  public void deleteProjectProperties() {
-    underTest.deleteProjectProperties(null, null);
-
-    assertNoInteraction();
-
-  }
-
-  @Test
-  public void deleteProjectProperties1() {
-    underTest.deleteProjectProperties(null, null, dbSession);
-
-    assertNoInteraction();
-  }
-
-  @Test
-  public void deleteGlobalProperty() {
-    underTest.deleteGlobalProperty(null);
-
-    assertNoInteraction();
-  }
-
-  @Test
-  public void deleteGlobalProperty1() {
-    underTest.deleteGlobalProperty(null, dbSession);
-
-    assertNoInteraction();
-  }
-
-  @Test
-  public void insertGlobalProperties() {
-    underTest.saveGlobalProperties(null);
-
-    assertNoInteraction();
-  }
-
-  @Test
-  public void renamePropertyKey() {
-    underTest.renamePropertyKey(null, null);
-
-    assertNoInteraction();
-  }
-
-  @Test
-  public void saveProperty() {
-    underTest.saveProperty(oldPropertyDto);
-
-    assertNoInteraction();
-  }
-
-  private void assertNoInteraction() {
-    verifyNoMoreInteractions(myBatis, dbSession, propertyDto);
-  }
-}
index eeb629b66cd707c078cee2233e46753160b34a5d..5b8996e43d9bfb5527a37d7793cd614db62f96cf 100644 (file)
@@ -138,7 +138,8 @@ class PurgeCommands {
     profiler.stop();
   }
 
-  void purgeDisabledComponents(String rootComponentUuid, Collection<String> disabledComponentUuids, PurgeListener listener) {
+  void purgeDisabledComponents(String rootComponentUuid, Collection<String> disabledComponentUuids, PurgeListener listener,
+    boolean measuresMigrationEnabled) {
     Set<String> missedDisabledComponentUuids = new HashSet<>();
 
     profiler.start("purgeDisabledComponents (file_sources)");
@@ -171,6 +172,18 @@ class PurgeCommands {
         }));
     profiler.stop();
 
+    if (measuresMigrationEnabled) {
+      profiler.start("purgeDisabledComponents (measures)");
+      missedDisabledComponentUuids.addAll(
+        executeLargeInputs(
+          purgeMapper.selectDisabledComponentsWithJsonMeasures(rootComponentUuid),
+          input -> {
+            purgeMapper.deleteJsonMeasuresByComponentUuids(input);
+            return input;
+          }));
+      profiler.stop();
+    }
+
     session.commit();
 
     // notify listener for any disabled component we found child data for which isn't part of the disabled components
@@ -459,6 +472,13 @@ class PurgeCommands {
     profiler.stop();
   }
 
+  void deleteJsonMeasures(String rootUuid) {
+    profiler.start("deleteJsonMeasures (measures)");
+    purgeMapper.deleteJsonMeasuresByBranchUuid(rootUuid);
+    session.commit();
+    profiler.stop();
+  }
+
   void deleteNewCodePeriods(String rootUuid) {
     profiler.start("deleteNewCodePeriods (new_code_periods)");
     purgeMapper.deleteNewCodePeriodsByRootUuid(rootUuid);
index 5220d33c2574b6d16a695ffe042e482d2cd368a0..7141bbdb3262b178b33537175f2d1626badfabc4 100644 (file)
@@ -40,10 +40,13 @@ import org.sonar.db.audit.model.ComponentNewValue;
 import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.BranchMapper;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertiesDao;
+import org.sonar.db.property.PropertyDto;
 
 import static java.util.Collections.emptyList;
 import static java.util.Optional.ofNullable;
 import static org.sonar.api.utils.DateUtils.dateToLong;
+import static org.sonar.core.config.CorePropertyDefinitions.SYSTEM_MEASURES_MIGRATION_ENABLED;
 import static org.sonar.db.DatabaseUtils.executeLargeInputs;
 
 public class PurgeDao implements Dao {
@@ -54,10 +57,12 @@ public class PurgeDao implements Dao {
 
   private final System2 system2;
   private final AuditPersister auditPersister;
+  private final PropertiesDao propertiesDao;
 
-  public PurgeDao(System2 system2, AuditPersister auditPersister) {
+  public PurgeDao(System2 system2, AuditPersister auditPersister, PropertiesDao propertiesDao) {
     this.system2 = system2;
     this.auditPersister = auditPersister;
+    this.propertiesDao = propertiesDao;
   }
 
   public void purge(DbSession session, PurgeConfiguration conf, PurgeListener listener, PurgeProfiler profiler) {
@@ -66,16 +71,18 @@ public class PurgeDao implements Dao {
     String rootUuid = conf.rootUuid();
     deleteAbortedAnalyses(rootUuid, commands);
     purgeAnalyses(commands, rootUuid);
-    purgeDisabledComponents(commands, conf, listener);
+    boolean measuresMigrationEnabled = isMeasuresMigrationEnabled();
+    purgeDisabledComponents(commands, conf, listener, measuresMigrationEnabled);
     deleteOldClosedIssues(conf, mapper, listener);
     purgeOldCeActivities(rootUuid, commands);
     purgeOldCeScannerContexts(rootUuid, commands);
 
     deleteOldDisabledComponents(commands, mapper, rootUuid);
-    purgeStaleBranches(commands, conf, mapper, rootUuid);
+    purgeStaleBranches(commands, conf, mapper, rootUuid, measuresMigrationEnabled);
   }
 
-  private static void purgeStaleBranches(PurgeCommands commands, PurgeConfiguration conf, PurgeMapper mapper, String rootUuid) {
+  private static void purgeStaleBranches(PurgeCommands commands, PurgeConfiguration conf, PurgeMapper mapper, String rootUuid,
+    boolean measuresMigrationEnabled) {
     Optional<Date> maxDate = conf.maxLiveDateOfInactiveBranches();
     if (maxDate.isEmpty()) {
       // not available if branch plugin is not installed
@@ -88,7 +95,7 @@ public class PurgeDao implements Dao {
 
     for (String branchUuid : branchUuids) {
       if (!rootUuid.equals(branchUuid)) {
-        deleteRootComponent(branchUuid, mapper, commands);
+        deleteRootComponent(branchUuid, mapper, commands, measuresMigrationEnabled);
       }
     }
   }
@@ -101,10 +108,11 @@ public class PurgeDao implements Dao {
     commands.purgeAnalyses(analysisUuids);
   }
 
-  private static void purgeDisabledComponents(PurgeCommands commands, PurgeConfiguration conf, PurgeListener listener) {
+  private static void purgeDisabledComponents(PurgeCommands commands, PurgeConfiguration conf, PurgeListener listener,
+    boolean measuresMigrationEnabled) {
     String rootUuid = conf.rootUuid();
     listener.onComponentsDisabling(rootUuid, conf.getDisabledComponentUuids());
-    commands.purgeDisabledComponents(rootUuid, conf.getDisabledComponentUuids(), listener);
+    commands.purgeDisabledComponents(rootUuid, conf.getDisabledComponentUuids(), listener, measuresMigrationEnabled);
   }
 
   private static void deleteOldClosedIssues(PurgeConfiguration conf, PurgeMapper mapper, PurgeListener listener) {
@@ -186,7 +194,7 @@ public class PurgeDao implements Dao {
     PurgeProfiler profiler = new PurgeProfiler();
     PurgeMapper purgeMapper = mapper(session);
     PurgeCommands purgeCommands = new PurgeCommands(session, profiler, system2);
-    deleteRootComponent(uuid, purgeMapper, purgeCommands);
+    deleteRootComponent(uuid, purgeMapper, purgeCommands, isMeasuresMigrationEnabled());
   }
 
   public void deleteProject(DbSession session, String uuid, String qualifier, String name, String key) {
@@ -194,15 +202,16 @@ public class PurgeDao implements Dao {
     PurgeMapper purgeMapper = mapper(session);
     PurgeCommands purgeCommands = new PurgeCommands(session, profiler, system2);
     long start = System2.INSTANCE.now();
+    boolean measuresMigrationEnabled = isMeasuresMigrationEnabled();
 
     List<String> branchUuids = session.getMapper(BranchMapper.class).selectByProjectUuid(uuid).stream()
       .map(BranchDto::getUuid)
       .filter(branchUuid -> !uuid.equals(branchUuid))
       .toList();
 
-    branchUuids.forEach(id -> deleteRootComponent(id, purgeMapper, purgeCommands));
+    branchUuids.forEach(id -> deleteRootComponent(id, purgeMapper, purgeCommands, measuresMigrationEnabled));
 
-    deleteRootComponent(uuid, purgeMapper, purgeCommands);
+    deleteRootComponent(uuid, purgeMapper, purgeCommands, measuresMigrationEnabled);
     auditPersister.deleteComponent(session, new ComponentNewValue(uuid, name, key, qualifier));
     logProfiling(profiler, start);
   }
@@ -218,7 +227,7 @@ public class PurgeDao implements Dao {
     LOG.info("");
   }
 
-  private static void deleteRootComponent(String rootUuid, PurgeMapper mapper, PurgeCommands commands) {
+  private static void deleteRootComponent(String rootUuid, PurgeMapper mapper, PurgeCommands commands, boolean measuresMigrationEnabled) {
     List<String> rootAndModulesOrSubviews = mapper.selectRootAndModulesOrSubviewsByProjectUuid(rootUuid);
     commands.deleteLinks(rootUuid);
     commands.deleteScannerCache(rootUuid);
@@ -231,6 +240,9 @@ public class PurgeDao implements Dao {
     commands.deleteWebhooks(rootUuid);
     commands.deleteWebhookDeliveries(rootUuid);
     commands.deleteLiveMeasures(rootUuid);
+    if (measuresMigrationEnabled) {
+      commands.deleteJsonMeasures(rootUuid);
+    }
     commands.deleteProjectMappings(rootUuid);
     commands.deleteProjectAlmSettings(rootUuid);
     commands.deletePermissions(rootUuid);
@@ -248,6 +260,13 @@ public class PurgeDao implements Dao {
     commands.deleteOutdatedProperties(rootUuid);
   }
 
+  private boolean isMeasuresMigrationEnabled() {
+    return Optional.ofNullable(propertiesDao.selectGlobalProperty(SYSTEM_MEASURES_MIGRATION_ENABLED))
+      .map(PropertyDto::getValue)
+      .map(Boolean::valueOf)
+      .orElse(false);
+  }
+
   /**
    * Delete the non root components (ie. sub-view, application or project copy) from the specified collection of {@link ComponentDto}
    * and data from their child tables.
index 182874c69e288214c7cf407f736f4224c9d1d1b1..50f5473cc71ce73bc152140018ce4983719e168c 100644 (file)
@@ -40,6 +40,8 @@ public interface PurgeMapper {
 
   Set<String> selectDisabledComponentsWithLiveMeasures(@Param("branchUuid") String branchUuid);
 
+  Set<String> selectDisabledComponentsWithJsonMeasures(@Param("branchUuid") String branchUuid);
+
   void deleteAnalyses(@Param("analysisUuids") List<String> analysisUuids);
 
   void deleteAnalysisProperties(@Param("analysisUuids") List<String> analysisUuids);
@@ -165,8 +167,12 @@ public interface PurgeMapper {
 
   void deleteLiveMeasuresByProjectUuid(@Param("projectUuid") String projectUuid);
 
+  void deleteJsonMeasuresByBranchUuid(@Param("branchUuid") String branchUuid);
+
   void deleteLiveMeasuresByComponentUuids(@Param("componentUuids") List<String> componentUuids);
 
+  void deleteJsonMeasuresByComponentUuids(@Param("componentUuids") List<String> componentUuids);
+
   void deleteNewCodePeriodsByRootUuid(String rootUuid);
 
   void deleteProjectAlmSettingsByProjectUuid(@Param("projectUuid") String projectUuid);
index 6105013fc92bfd0bf8707c240ad4603f45d2acac..1bba7511963448d9c4c659c43a6a8c1351a8374b 100644 (file)
       and p.branch_uuid=#{branchUuid,jdbcType=VARCHAR}
   </select>
 
+  <select id="selectDisabledComponentsWithJsonMeasures" parameterType="map" resultType="String">
+    select
+      m.component_uuid
+    from measures m
+    inner join components p on
+      p.uuid = m.component_uuid
+      and p.enabled = ${_false}
+      and p.branch_uuid=#{branchUuid,jdbcType=VARCHAR}
+  </select>
+
   <delete id="deleteAnalysisMeasures" parameterType="map">
     delete from project_measures
     where
     delete from live_measures where project_uuid = #{projectUuid,jdbcType=VARCHAR}
   </delete>
 
+  <delete id="deleteJsonMeasuresByBranchUuid">
+    delete from measures where branch_uuid = #{branchUuid,jdbcType=VARCHAR}
+  </delete>
+
   <delete id="deleteLiveMeasuresByComponentUuids">
     delete from live_measures where component_uuid in <foreach item="componentUuid" index="index" collection="componentUuids" open="("
                                                                separator="," close=")">#{componentUuid, jdbcType=VARCHAR}</foreach>
   </delete>
 
+  <delete id="deleteJsonMeasuresByComponentUuids">
+    delete from measures where component_uuid in <foreach item="componentUuid" index="index" collection="componentUuids" open="("
+                                                               separator="," close=")">#{componentUuid, jdbcType=VARCHAR}</foreach>
+  </delete>
+
   <delete id="deleteUserDismissedMessagesByProjectUuid">
     delete from user_dismissed_messages where project_uuid = #{projectUuid,jdbcType=VARCHAR}
   </delete>
     delete from scanner_analysis_cache where branch_uuid = #{branchUuid,jdbcType=VARCHAR}
   </delete>
 </mapper>
-
index 6cbc6b7517e25ce40076bfd55a99c78a89598460..56e2e7fdc5b5587785449a9ac31d66418f7a9971 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.db.purge;
 import com.google.common.collect.ImmutableSet;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.sql.SQLException;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.Arrays;
@@ -70,6 +71,7 @@ import org.sonar.db.event.EventDto;
 import org.sonar.db.event.EventTesting;
 import org.sonar.db.issue.IssueChangeDto;
 import org.sonar.db.issue.IssueDto;
+import org.sonar.db.measure.JsonMeasureDto;
 import org.sonar.db.measure.LiveMeasureDto;
 import org.sonar.db.measure.MeasureDto;
 import org.sonar.db.metric.MetricDto;
@@ -84,6 +86,7 @@ import org.sonar.db.user.UserDismissedMessageDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.db.webhook.WebhookDeliveryLiteDto;
 import org.sonar.db.webhook.WebhookDto;
+import org.sonar.server.platform.db.migration.adhoc.CreateMeasuresTable;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -98,6 +101,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
+import static org.sonar.core.config.CorePropertyDefinitions.SYSTEM_MEASURES_MIGRATION_ENABLED;
 import static org.sonar.db.ce.CeTaskTypes.REPORT;
 import static org.sonar.db.component.ComponentTesting.newBranchDto;
 import static org.sonar.db.component.ComponentTesting.newDirectory;
@@ -267,7 +271,7 @@ public class PurgeDaoTest {
   }
 
   @Test
-  public void close_issues_clean_index_and_file_sources_of_disabled_components_specified_by_uuid_in_configuration() {
+  public void close_issues_clean_index_and_file_sources_of_disabled_components_specified_by_uuid_in_configuration() throws SQLException {
     RuleDto rule = db.rules().insert();
     ComponentDto project = db.components().insertPublicProject();
     db.components().insertSnapshot(project);
@@ -306,6 +310,20 @@ public class PurgeDaoTest {
     LiveMeasureDto liveMeasureMetric1OnNonSelected = db.measures().insertLiveMeasure(enabledFile, metric1);
     LiveMeasureDto liveMeasureMetric2OnNonSelected = db.measures().insertLiveMeasure(enabledFile, metric2);
     assertThat(db.countRowsOfTable("live_measures")).isEqualTo(8);
+
+    createMeasuresTable();
+    db.properties().insertProperty(SYSTEM_MEASURES_MIGRATION_ENABLED, "true", null);
+
+    db.measures().insertJsonMeasure(srcFile,
+      m -> m.addValue(metric1.getKey(), RandomUtils.nextInt(50)).addValue(metric2.getKey(), RandomUtils.nextInt(50)));
+    db.measures().insertJsonMeasure(dir,
+      m -> m.addValue(metric1.getKey(), RandomUtils.nextInt(50)).addValue(metric2.getKey(), RandomUtils.nextInt(50)));
+    db.measures().insertJsonMeasure(project,
+      m -> m.addValue(metric1.getKey(), RandomUtils.nextInt(50)).addValue(metric2.getKey(), RandomUtils.nextInt(50)));
+    db.measures().insertJsonMeasure(enabledFile,
+      m -> m.addValue(metric1.getKey(), RandomUtils.nextInt(50)).addValue(metric2.getKey(), RandomUtils.nextInt(50)));
+    assertThat(db.countRowsOfTable("measures")).isEqualTo(4);
+
     PurgeListener purgeListener = mock(PurgeListener.class);
 
     // back to present
@@ -348,6 +366,18 @@ public class PurgeDaoTest {
     assertThat(liveMeasureDtos)
       .extracting(LiveMeasureDto::getMetricUuid)
       .containsOnly(metric1.getUuid(), metric2.getUuid());
+
+    // delete json measures of selected
+    assertThat(db.countRowsOfTable("measures")).isEqualTo(2);
+    List<JsonMeasureDto> measureDtos = Set.of(srcFile.uuid(), dir.uuid(), project.uuid(), enabledFile.uuid()).stream()
+      .map(component -> db.getDbClient().jsonMeasureDao().selectByComponentUuid(dbSession, component))
+      .filter(Optional::isPresent).map(Optional::get).toList();
+    assertThat(measureDtos)
+      .extracting(JsonMeasureDto::getComponentUuid)
+      .containsOnly(enabledFile.uuid(), project.uuid());
+    assertThat(measureDtos)
+      .allSatisfy(dto -> assertThat(dto.getMetricValues())
+        .containsOnlyKeys(metric1.getKey(), metric2.getKey()));
   }
 
   @Test
@@ -1517,23 +1547,67 @@ public class PurgeDaoTest {
   }
 
   @Test
-  public void delete_live_measures_when_deleting_project() {
+  public void delete_live_measures_when_deleting_project() throws SQLException {
+    createMeasuresTable();
+    db.properties().insertProperty(SYSTEM_MEASURES_MIGRATION_ENABLED, "true", null);
+
     MetricDto metric = db.measures().insertMetric();
 
     ComponentDto project1 = db.components().insertPublicProject();
     ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project1));
     db.measures().insertLiveMeasure(project1, metric);
     db.measures().insertLiveMeasure(module1, metric);
+    db.measures().insertJsonMeasure(project1, m -> m.addValue(metric.getKey(), RandomUtils.nextInt(50)));
+    db.measures().insertJsonMeasure(module1, m -> m.addValue(metric.getKey(), RandomUtils.nextInt(50)));
 
     ComponentDto project2 = db.components().insertPublicProject();
     ComponentDto module2 = db.components().insertComponent(ComponentTesting.newModuleDto(project2));
     db.measures().insertLiveMeasure(project2, metric);
     db.measures().insertLiveMeasure(module2, metric);
+    db.measures().insertJsonMeasure(project2, m -> m.addValue(metric.getKey(), RandomUtils.nextInt(50)));
+    db.measures().insertJsonMeasure(module2, m -> m.addValue(metric.getKey(), RandomUtils.nextInt(50)));
+
+    assertThat(db.countRowsOfTable("live_measures")).isEqualTo(4);
+    assertThat(db.countRowsOfTable("measures")).isEqualTo(4);
 
     underTest.deleteProject(dbSession, project1.uuid(), project1.qualifier(), project1.name(), project1.getKey());
 
+    assertThat(db.countRowsOfTable("live_measures")).isEqualTo(2);
+    assertThat(db.countRowsOfTable("measures")).isEqualTo(2);
     assertThat(dbClient.liveMeasureDao().selectByComponentUuidsAndMetricUuids(dbSession, asList(project1.uuid(), module1.uuid()), asList(metric.getUuid()))).isEmpty();
     assertThat(dbClient.liveMeasureDao().selectByComponentUuidsAndMetricUuids(dbSession, asList(project2.uuid(), module2.uuid()), asList(metric.getUuid()))).hasSize(2);
+    assertThat(dbClient.jsonMeasureDao().selectByComponentUuid(dbSession, project1.uuid())).isEmpty();
+    assertThat(dbClient.jsonMeasureDao().selectByComponentUuid(dbSession, module1.uuid())).isEmpty();
+    assertThat(dbClient.jsonMeasureDao().selectByComponentUuid(dbSession, project2.uuid())).isNotEmpty();
+    assertThat(dbClient.jsonMeasureDao().selectByComponentUuid(dbSession, module2.uuid())).isNotEmpty();
+  }
+
+  @Test
+  public void do_not_delete_json_measures_when_migration_disabled() throws SQLException {
+    createMeasuresTable();
+    db.properties().insertProperty(SYSTEM_MEASURES_MIGRATION_ENABLED, "false", null);
+
+    MetricDto metric = db.measures().insertMetric();
+
+    ComponentDto project1 = db.components().insertPublicProject();
+    ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project1));
+    db.measures().insertLiveMeasure(project1, metric);
+    db.measures().insertLiveMeasure(module1, metric);
+    db.measures().insertJsonMeasure(project1, m -> m.addValue(metric.getKey(), RandomUtils.nextInt(50)));
+    db.measures().insertJsonMeasure(module1, m -> m.addValue(metric.getKey(), RandomUtils.nextInt(50)));
+
+    assertThat(db.countRowsOfTable("live_measures")).isEqualTo(2);
+    assertThat(db.countRowsOfTable("measures")).isEqualTo(2);
+
+    underTest.deleteProject(dbSession, project1.uuid(), project1.qualifier(), project1.name(), project1.getKey());
+
+    assertThat(db.countRowsOfTable("live_measures")).isZero();
+    assertThat(db.countRowsOfTable("measures")).isEqualTo(2);
+  }
+
+  private void createMeasuresTable() throws SQLException {
+    new CreateMeasuresTable(db.getDbClient().getDatabase()).execute();
+    db.executeDdl("truncate table measures");
   }
 
   private void verifyNoEffect(ComponentDto firstRoot, ComponentDto... otherRoots) {
index f41c48d76e227c2cd7b25b64829359778e99f5ce..ccedabf1c16ce7baa9e4fc8b281c8c8f0c6ae96b 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.db.DbTester;
 import org.sonar.db.audit.AuditPersister;
 import org.sonar.db.audit.model.ComponentNewValue;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertiesDao;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
@@ -50,7 +51,7 @@ public class PurgeDaoWithAuditTest {
 
   private final DbSession dbSession = db.getSession();
   private final AuditPersister auditPersister = mock(AuditPersister.class);
-  private final PurgeDao underTestWithPersister = new PurgeDao(system2, auditPersister);
+  private final PurgeDao underTestWithPersister = new PurgeDao(system2, auditPersister, mock(PropertiesDao.class));
 
   @Test
   public void delete_project_persist_audit_with_uuid_and_name() {
index a036b7a104dca030447783fb2f345ef9a559ac8f..49e0b34725795b9b753f86588ef2c3bc5b25b801 100644 (file)
@@ -78,6 +78,18 @@ public class MeasureDbTester {
     return dto;
   }
 
+  @SafeVarargs
+  public final JsonMeasureDto insertJsonMeasure(ComponentDto component, Consumer<JsonMeasureDto>... consumers) {
+    JsonMeasureDto dto = new JsonMeasureDto()
+      .setComponentUuid(component.uuid())
+      .setBranchUuid(component.branchUuid());
+    Arrays.stream(consumers).forEach(c -> c.accept(dto));
+    dto.computeJsonValueHash();
+    dbClient.jsonMeasureDao().insert(db.getSession(), dto);
+    db.commit();
+    return dto;
+  }
+
   @SafeVarargs
   public final MetricDto insertMetric(Consumer<MetricDto>... consumers) {
     MetricDto metricDto = newMetricDto();