aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClaire Villard <60586848+claire-villard-sonarsource@users.noreply.github.com>2024-09-27 09:57:19 +0200
committersonartech <sonartech@sonarsource.com>2024-10-09 20:02:47 +0000
commite8ef7e9b39e0ea580d82df8b7b4fabbcc6e41f01 (patch)
treed5a488342abfecf96e914f18cf735c769901b9eb
parenta79666b02068f247ea26944d3ea0d0181365c3b7 (diff)
downloadsonarqube-e8ef7e9b39e0ea580d82df8b7b4fabbcc6e41f01.tar.gz
sonarqube-e8ef7e9b39e0ea580d82df8b7b4fabbcc6e41f01.zip
SONAR-22880 Migrate 'live_measures' to 'measures'
-rw-r--r--server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepIT.java (renamed from server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepTest.java)38
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStep.java29
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/component/BranchDaoIT.java21
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/portfolio/PortfolioDaoIT.java29
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java6
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java3
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java5
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java2
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml9
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml9
-rw-r--r--server/sonar-db-dao/src/schema/schema-sq.ddl8
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTableIT.java50
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTableIT.java50
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigratedIT.java53
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigratedIT.java54
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasuresIT.java184
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasuresIT.java182
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MassUpdate.java4
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractAddMeasuresMigratedColumnToTable.java56
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractCreateIndexOnMeasuresMigrated.java53
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractMigrateLiveMeasuresToMeasures.java204
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTable.java31
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTable.java31
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigrated.java32
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigrated.java32
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java11
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasures.java30
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasures.java30
28 files changed, 1231 insertions, 15 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepTest.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepIT.java
index 7bcbd114609..dccaf1e4920 100644
--- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepIT.java
@@ -21,6 +21,7 @@ package org.sonar.ce.task.projectanalysis.step;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -39,11 +40,15 @@ import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
import org.sonar.ce.task.step.TestComputationStepContext;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.PortfolioData;
import org.sonar.db.measure.MeasureDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.server.project.Project;
+import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
@@ -61,7 +66,7 @@ import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW
import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
-class PersistMeasuresStepTest {
+class PersistMeasuresStepIT {
private static final Metric<?> STRING_METRIC = new Metric.Builder("string-metric", "String metric", Metric.ValueType.STRING).create();
private static final Metric<?> INT_METRIC = new Metric.Builder("int-metric", "int metric", Metric.ValueType.INT).create();
@@ -126,6 +131,7 @@ class PersistMeasuresStepTest {
.hasValueSatisfying(measure -> assertThat(measure.getMetricValues()).containsEntry(STRING_METRIC.getKey(), "dir-value"));
assertThat(selectMeasure("file-uuid"))
.hasValueSatisfying(measure -> assertThat(measure.getMetricValues()).containsEntry(STRING_METRIC.getKey(), "file-value"));
+ assertThat(getBranchMigratedFlag("project-uuid")).isTrue();
verifyInsertsOrUpdates(3);
}
@@ -147,6 +153,7 @@ class PersistMeasuresStepTest {
.hasValueSatisfying(measure -> assertThat(measure.getMetricValues()).containsEntry(STRING_METRIC.getKey(), "subview-value"));
assertThat(selectMeasure("project-uuid"))
.hasValueSatisfying(measure -> assertThat(measure.getMetricValues()).containsEntry(STRING_METRIC.getKey(), "project-value"));
+ assertThat(getPortfolioMigratedFlag("view-uuid")).isTrue();
verifyInsertsOrUpdates(3);
}
@@ -162,6 +169,7 @@ class PersistMeasuresStepTest {
Component project = ReportComponent.builder(Component.Type.PROJECT, -1).setUuid("project-uuid")
.addChildren(files.toArray(Component[]::new))
.build();
+ insertBranch();
treeRootHolder.setRoot(project);
analysisMetadataHolder.setBaseAnalysis(new Analysis.Builder().setUuid("uuid").setCreatedAt(1L).build());
insertMeasure("file-uuid0", "project-uuid", STRING_METRIC, valuePrefix + "0");
@@ -177,6 +185,7 @@ class PersistMeasuresStepTest {
// all measures are persisted, for project and all files
assertThat(db.countRowsOfTable("measures")).isEqualTo(num + 1);
+ assertThat(getBranchMigratedFlag("project-uuid")).isTrue();
verifyInsertsOrUpdates(num - 1);
verifyUnchanged(1);
verify(computeDuplicationDataMeasure, times(num)).compute(any(Component.class));
@@ -283,6 +292,7 @@ class PersistMeasuresStepTest {
step().execute(context);
+ assertThat(getBranchMigratedFlag("project-uuid")).isTrue();
verifyInsertsOrUpdates(0);
verifyUnchanged(1);
}
@@ -333,6 +343,9 @@ class PersistMeasuresStepTest {
insertComponent("project-key", "project-uuid");
insertComponent("dir-key", "dir-uuid");
insertComponent("file-key", "file-uuid");
+
+ // branch is persisted in db
+ insertBranch();
}
private void preparePortfolio() {
@@ -348,16 +361,21 @@ class PersistMeasuresStepTest {
treeRootHolder.setRoot(portfolio);
// components as persisted in db
- ComponentDto portfolioDto = insertComponent("view-key", "view-uuid");
+ PortfolioData portfolioDto1 = db.components().insertPrivatePortfolioData(c -> c.setUuid("view-uuid").setKey("view-key").setBranchUuid("view-uuid"));
insertComponent("subview-key", "subview-uuid");
insertComponent("project-key", "project-uuid");
- analysisMetadataHolder.setProject(Project.from(portfolioDto));
+ analysisMetadataHolder.setProject(Project.from(portfolioDto1.getPortfolioDto()));
}
private Optional<MeasureDto> selectMeasure(String componentUuid) {
return dbClient.measureDao().selectByComponentUuid(db.getSession(), componentUuid);
}
+ private void insertBranch() {
+ dbClient.branchDao().insert(db.getSession(), new BranchDto().setUuid("project-uuid").setProjectUuid("project-uuid").setKey("branch").setBranchType(BranchType.BRANCH).setIsMain(true));
+ db.commit();
+ }
+
private ComponentDto insertComponent(String key, String uuid) {
ComponentDto componentDto = new ComponentDto()
.setKey(key)
@@ -372,6 +390,20 @@ class PersistMeasuresStepTest {
return new PersistMeasuresStep(dbClient, metricRepository, treeRootHolder, measureRepository, computeDuplicationDataMeasure);
}
+ private boolean getBranchMigratedFlag(String branch) {
+ List<Map<String, Object>> result = db.select(format("select measures_migrated as \"MIGRATED\" from project_branches where uuid = '%s'", branch));
+ assertThat(result).hasSize(1);
+
+ return (boolean) result.get(0).get("MIGRATED");
+ }
+
+ private boolean getPortfolioMigratedFlag(String portfolio) {
+ List<Map<String, Object>> result = db.select(format("select measures_migrated as \"MIGRATED\" from portfolios where uuid = '%s'", portfolio));
+ assertThat(result).hasSize(1);
+
+ return (boolean) result.get(0).get("MIGRATED");
+ }
+
private void verifyInsertsOrUpdates(int expectedInsertsOrUpdates) {
context.getStatistics().assertValue("insertsOrUpdates", expectedInsertsOrUpdates);
}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStep.java
index d0d0513e85f..bf5ae6856f6 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStep.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStep.java
@@ -30,6 +30,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.Component.Type;
import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
@@ -133,12 +134,36 @@ public class PersistMeasuresStep implements ComputationStep {
}
}
persist(inserts, updates);
+ updateMeasureMigratedFlag();
context.getStatistics()
.add("insertsOrUpdates", insertsOrUpdates)
.add("unchanged", unchanged);
}
+ private void updateMeasureMigratedFlag() {
+ Type type = treeRootHolder.getRoot().getType();
+ if (type == Type.PROJECT) {
+ persistBranchFlag();
+ } else if (type == Type.VIEW) {
+ persistPortfolioFlag();
+ }
+ }
+
+ private void persistBranchFlag() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ dbClient.branchDao().updateMeasuresMigrated(dbSession, treeRootHolder.getRoot().getUuid(), true);
+ dbSession.commit();
+ }
+ }
+
+ private void persistPortfolioFlag() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ dbClient.portfolioDao().updateMeasuresMigrated(dbSession, treeRootHolder.getRoot().getUuid(), true);
+ dbSession.commit();
+ }
+ }
+
private Set<MeasureHash> getDBMeasureHashes() {
try (DbSession dbSession = dbClient.openSession(false)) {
return dbClient.measureDao().selectMeasureHashesForBranch(dbSession, treeRootHolder.getRoot().getUuid());
@@ -153,7 +178,7 @@ public class PersistMeasuresStep implements ComputationStep {
Map<String, Measure> measures = measureRepository.getRawMeasures(component);
for (Map.Entry<String, Measure> measuresByMetricKey : measures.entrySet()) {
String metricKey = measuresByMetricKey.getKey();
- if (NOT_TO_PERSIST_ON_FILE_METRIC_KEYS.contains(metricKey) && component.getType() == Component.Type.FILE) {
+ if (NOT_TO_PERSIST_ON_FILE_METRIC_KEYS.contains(metricKey) && component.getType() == Type.FILE) {
continue;
}
Metric metric = metricRepository.getByKey(metricKey);
@@ -166,7 +191,7 @@ public class PersistMeasuresStep implements ComputationStep {
.filter(Objects::nonNull)
.forEach(value -> measureDto.addValue(metric.getKey(), value));
- if (component.getType() == Component.Type.FILE) {
+ if (component.getType() == Type.FILE) {
if (computeDuplicationDataMeasure == null) {
throw new IllegalStateException("ComputeDuplicationDataMeasure not initialized in container");
}
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/component/BranchDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/component/BranchDaoIT.java
index f5096c9a414..77ac376f26b 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/component/BranchDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/component/BranchDaoIT.java
@@ -42,6 +42,7 @@ import org.sonar.db.metric.MetricDto;
import org.sonar.db.project.ProjectDto;
import org.sonar.db.protobuf.DbProjectBranches;
+import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@@ -751,6 +752,26 @@ class BranchDaoIT {
}
@Test
+ void updateMeasuresMigrated() {
+ ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
+ String uuid1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH)).uuid();
+ String uuid2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH)).uuid();
+
+ underTest.updateMeasuresMigrated(dbSession, uuid1, true);
+ underTest.updateMeasuresMigrated(dbSession, uuid2, false);
+
+ assertThat(getMeasuresMigrated(uuid1)).isTrue();
+ assertThat(getMeasuresMigrated(uuid2)).isFalse();
+ }
+
+ private boolean getMeasuresMigrated(String uuid1) {
+ List<Map<String, Object>> select = db.select(dbSession, format("select measures_migrated from project_branches where uuid = '%s'", uuid1));
+
+ assertThat(select).hasSize(1);
+ return (boolean) select.get(0).get("measures_migrated");
+ }
+
+ @Test
void doAnyOfComponentsNeedIssueSync() {
assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, emptyList())).isFalse();
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/portfolio/PortfolioDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/portfolio/PortfolioDaoIT.java
index 42c1052f2fb..eb544fde07a 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/portfolio/PortfolioDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/portfolio/PortfolioDaoIT.java
@@ -21,11 +21,13 @@ package org.sonar.db.portfolio;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
@@ -37,6 +39,7 @@ import org.sonar.db.component.ProjectData;
import org.sonar.db.project.ApplicationProjectDto;
import org.sonar.db.project.ProjectDto;
+import static java.lang.String.format;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
@@ -50,6 +53,8 @@ import static org.mockito.Mockito.verify;
class PortfolioDaoIT {
private final System2 system2 = new AlwaysIncreasingSystem2(1L, 1);
+ private final SequenceUuidFactory uuidFactory = new SequenceUuidFactory();
+
@RegisterExtension
private final DbTester db = DbTester.create(system2);
private final AuditPersister audit = mock(AuditPersister.class);
@@ -246,6 +251,25 @@ class PortfolioDaoIT {
}
@Test
+ void update_measures_migrated() {
+ PortfolioDto portfolio1 = db.components().insertPrivatePortfolioDto("name1");
+ PortfolioDto portfolio2 = db.components().insertPrivatePortfolioDto("name2");
+
+ portfolioDao.updateMeasuresMigrated(session, portfolio1.getUuid(), true);
+ portfolioDao.updateMeasuresMigrated(session, portfolio2.getUuid(), false);
+
+ assertThat(getMeasuresMigrated(portfolio1.getUuid())).isTrue();
+ assertThat(getMeasuresMigrated(portfolio2.getUuid())).isFalse();
+ }
+
+ private boolean getMeasuresMigrated(String uuid1) {
+ List<Map<String, Object>> select = db.select(session, format("select measures_migrated from portfolios where uuid = '%s'", uuid1));
+
+ assertThat(select).hasSize(1);
+ return (boolean) select.get(0).get("measures_migrated");
+ }
+
+ @Test
void selectAllReferencesToPortfolios() {
db.components().insertPrivatePortfolioDto("portfolio1");
db.components().insertPrivatePortfolioDto("portfolio2");
@@ -523,7 +547,8 @@ class PortfolioDaoIT {
@Test
void deleteReferencesTo_with_non_existing_reference_doesnt_fail() {
- portfolioDao.deleteReferencesTo(session, "portfolio3");
+ assertThatCode(() -> portfolioDao.deleteReferencesTo(session, "portfolio3"))
+ .doesNotThrowAnyException();
}
@Test
@@ -737,7 +762,7 @@ class PortfolioDaoIT {
}
private PortfolioDto addPortfolio(PortfolioDto parent) {
- return addPortfolio(parent, UuidFactoryFast.getInstance().create());
+ return addPortfolio(parent, uuidFactory.create());
}
private PortfolioDto addPortfolio(PortfolioDto parent, String uuid) {
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
index bf0d7bf582f..31479d4b5a3 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java
@@ -177,11 +177,17 @@ public class BranchDao implements Dao {
long now = system2.now();
return mapper(dbSession).updateNeedIssueSync(branchUuid, needIssueSync, now);
}
+
public long updateIsMain(DbSession dbSession, String branchUuid, boolean isMain) {
long now = system2.now();
return mapper(dbSession).updateIsMain(branchUuid, isMain, now);
}
+ public long updateMeasuresMigrated(DbSession dbSession, String branchUuid, boolean measuresMigrated) {
+ long now = system2.now();
+ return mapper(dbSession).updateMeasuresMigrated(branchUuid, measuresMigrated, now);
+ }
+
public boolean doAnyOfComponentsNeedIssueSync(DbSession session, List<String> components) {
if (!components.isEmpty()) {
List<Boolean> result = new LinkedList<>();
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
index 65fc908814b..536d3cb7701 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java
@@ -66,7 +66,7 @@ public interface BranchMapper {
long updateAllNeedIssueSyncForProject(@Param("projectUuid") String projectUuid, @Param("now") long now);
- long updateNeedIssueSync(@Param("uuid") String uuid, @Param("needIssueSync")boolean needIssueSync,@Param("now") long now);
+ long updateNeedIssueSync(@Param("uuid") String uuid, @Param("needIssueSync") boolean needIssueSync, @Param("now") long now);
long updateIsMain(@Param("uuid") String uuid, @Param("isMain") boolean isMain, @Param("now") long now);
@@ -78,4 +78,5 @@ public interface BranchMapper {
List<BranchMeasuresDto> selectBranchMeasuresWithCaycMetric(long yesterday);
+ int updateMeasuresMigrated(@Param("uuid") String uuid, @Param("measuresMigrated") boolean measuresMigrated, @Param("now") long now);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java
index 2f88082ed13..cd3152a7ad9 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioDao.java
@@ -248,6 +248,11 @@ public class PortfolioDao implements Dao {
return uuid;
}
+ public long updateMeasuresMigrated(DbSession dbSession, String branchUuid, boolean measuresMigrated) {
+ long now = system2.now();
+ return mapper(dbSession).updateMeasuresMigrated(branchUuid, measuresMigrated, now);
+ }
+
public void deleteProjects(DbSession dbSession, String portfolioUuid) {
mapper(dbSession).deleteProjects(portfolioUuid);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java
index 05c6225719e..218ecc688bf 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/portfolio/PortfolioMapper.java
@@ -116,4 +116,6 @@ public interface PortfolioMapper {
List<PortfolioDto> selectRootOfReferencersToAppBranch(@Param("appUuid") String appUuid, @Param("appBranchKey") String appBranchKey);
List<KeyWithUuidDto> selectUuidsByKey(@Param("rootKey") String rootKey);
+
+ int updateMeasuresMigrated(@Param("uuid") String uuid, @Param("measuresMigrated") boolean measuresMigrated, @Param("now") long now);
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
index b2b3993d2bf..d90e2846960 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml
@@ -278,6 +278,15 @@
uuid = #{uuid, jdbcType=VARCHAR}
</update>
+ <update id="updateMeasuresMigrated">
+ update project_branches
+ set
+ measures_migrated = #{measuresMigrated, jdbcType=BOOLEAN},
+ updated_at = #{now, jdbcType=BIGINT}
+ where
+ uuid = #{uuid, jdbcType=VARCHAR}
+ </update>
+
<sql id="doAnyOfComponentsNeedIssueSyncSql">
select
case when exists
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml
index 2071d83028c..22f0c0ff611 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/portfolio/PortfolioMapper.xml
@@ -560,4 +560,13 @@
WHERE
uuid = #{uuid,jdbcType=VARCHAR}
</update>
+
+ <update id="updateMeasuresMigrated">
+ update portfolios
+ set
+ measures_migrated = #{measuresMigrated, jdbcType=BOOLEAN},
+ updated_at = #{now, jdbcType=BIGINT}
+ where
+ uuid = #{uuid, jdbcType=VARCHAR}
+ </update>
</mapper>
diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl
index f19c8ed7a70..9eea010c9f6 100644
--- a/server/sonar-db-dao/src/schema/schema-sq.ddl
+++ b/server/sonar-db-dao/src/schema/schema-sq.ddl
@@ -699,10 +699,12 @@ CREATE TABLE "PORTFOLIOS"(
"SELECTION_EXPRESSION" CHARACTER VARYING(4000),
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL,
- "BRANCH_KEY" CHARACTER VARYING(255)
+ "BRANCH_KEY" CHARACTER VARYING(255),
+ "MEASURES_MIGRATED" BOOLEAN DEFAULT FALSE NOT NULL
);
ALTER TABLE "PORTFOLIOS" ADD CONSTRAINT "PK_PORTFOLIOS" PRIMARY KEY("UUID");
CREATE UNIQUE NULLS NOT DISTINCT INDEX "UNIQ_PORTFOLIOS_KEE" ON "PORTFOLIOS"("KEE" NULLS FIRST);
+CREATE INDEX "PORTFOLIOS_MEASURES_MIGRATED" ON "PORTFOLIOS"("MEASURES_MIGRATED" NULLS FIRST);
CREATE TABLE "PROJECT_ALM_SETTINGS"(
"UUID" CHARACTER VARYING(40) NOT NULL,
@@ -742,11 +744,13 @@ CREATE TABLE "PROJECT_BRANCHES"(
"UPDATED_AT" BIGINT NOT NULL,
"EXCLUDE_FROM_PURGE" BOOLEAN DEFAULT FALSE NOT NULL,
"NEED_ISSUE_SYNC" BOOLEAN NOT NULL,
- "IS_MAIN" BOOLEAN NOT NULL
+ "IS_MAIN" BOOLEAN NOT NULL,
+ "MEASURES_MIGRATED" BOOLEAN DEFAULT FALSE NOT NULL
);
ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID");
CREATE UNIQUE NULLS NOT DISTINCT INDEX "UNIQ_PROJECT_BRANCHES" ON "PROJECT_BRANCHES"("BRANCH_TYPE" NULLS FIRST, "PROJECT_UUID" NULLS FIRST, "KEE" NULLS FIRST);
CREATE INDEX "PROJECT_BRANCHES_PROJECT_UUID" ON "PROJECT_BRANCHES"("PROJECT_UUID" NULLS FIRST);
+CREATE INDEX "PB_MEASURES_MIGRATED" ON "PROJECT_BRANCHES"("MEASURES_MIGRATED" NULLS FIRST);
CREATE TABLE "PROJECT_LINKS"(
"UUID" CHARACTER VARYING(40) NOT NULL,
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTableIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTableIT.java
new file mode 100644
index 00000000000..000681ae3b6
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTableIT.java
@@ -0,0 +1,50 @@
+/*
+ * 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.v107;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.db.MigrationDbTester;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
+import static org.sonar.server.platform.db.migration.version.v107.AddMeasuresMigratedColumnToPortfoliosTable.MIGRATION_FLAG_COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.version.v107.AddMeasuresMigratedColumnToPortfoliosTable.PORTFOLIOS_TABLE_NAME;
+
+class AddMeasuresMigratedColumnToPortfoliosTableIT {
+
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(AddMeasuresMigratedColumnToPortfoliosTable.class);
+ private final AddMeasuresMigratedColumnToPortfoliosTable underTest = new AddMeasuresMigratedColumnToPortfoliosTable(db.database());
+
+ @Test
+ void execute_whenColumnDoesNotExist_shouldCreateColumn() throws SQLException {
+ db.assertColumnDoesNotExist(PORTFOLIOS_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME);
+ underTest.execute();
+ db.assertColumnDefinition(PORTFOLIOS_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME, Types.BOOLEAN, null, false);
+ }
+
+ @Test
+ void execute_whenColumnAlreadyExists_shouldNotFail() throws SQLException {
+ underTest.execute();
+ assertThatCode(underTest::execute).doesNotThrowAnyException();
+ }
+}
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTableIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTableIT.java
new file mode 100644
index 00000000000..8971111f7ee
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTableIT.java
@@ -0,0 +1,50 @@
+/*
+ * 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.v107;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.db.MigrationDbTester;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
+import static org.sonar.server.platform.db.migration.version.v107.AddMeasuresMigratedColumnToProjectBranchesTable.MIGRATION_FLAG_COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.version.v107.AddMeasuresMigratedColumnToProjectBranchesTable.PROJECT_BRANCHES_TABLE_NAME;
+
+class AddMeasuresMigratedColumnToProjectBranchesTableIT {
+
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(AddMeasuresMigratedColumnToProjectBranchesTable.class);
+ private final AddMeasuresMigratedColumnToProjectBranchesTable underTest = new AddMeasuresMigratedColumnToProjectBranchesTable(db.database());
+
+ @Test
+ void execute_whenColumnDoesNotExist_shouldCreateColumn() throws SQLException {
+ db.assertColumnDoesNotExist(PROJECT_BRANCHES_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME);
+ underTest.execute();
+ db.assertColumnDefinition(PROJECT_BRANCHES_TABLE_NAME, MIGRATION_FLAG_COLUMN_NAME, Types.BOOLEAN, null, false);
+ }
+
+ @Test
+ void execute_whenColumnAlreadyExists_shouldNotFail() throws SQLException {
+ underTest.execute();
+ assertThatCode(underTest::execute).doesNotThrowAnyException();
+ }
+}
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigratedIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigratedIT.java
new file mode 100644
index 00000000000..691c105d8ff
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigratedIT.java
@@ -0,0 +1,53 @@
+/*
+ * 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.v107;
+
+import java.sql.SQLException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.db.MigrationDbTester;
+
+import static org.sonar.server.platform.db.migration.version.v107.CreateIndexOnPortfoliosMeasuresMigrated.COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.version.v107.CreateIndexOnPortfoliosMeasuresMigrated.TABLE_NAME;
+
+class CreateIndexOnPortfoliosMeasuresMigratedIT {
+
+ private static final String INDEX_NAME = "portfolios_measures_migrated";
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(CreateIndexOnPortfoliosMeasuresMigrated.class);
+ private final CreateIndexOnPortfoliosMeasuresMigrated underTest = new CreateIndexOnPortfoliosMeasuresMigrated(db.database());
+
+ @Test
+ void migration_should_create_index() throws SQLException {
+ db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+
+ underTest.execute();
+
+ db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+ }
+
+ @Test
+ void migration_should_be_reentrant() throws SQLException {
+ underTest.execute();
+ underTest.execute();
+
+ db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+ }
+}
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigratedIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigratedIT.java
new file mode 100644
index 00000000000..3e95ed5611d
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigratedIT.java
@@ -0,0 +1,54 @@
+/*
+ * 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.v107;
+
+import java.sql.SQLException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.db.MigrationDbTester;
+
+import static org.sonar.server.platform.db.migration.version.v107.CreateIndexOnProjectBranchesMeasuresMigrated.COLUMN_NAME;
+import static org.sonar.server.platform.db.migration.version.v107.CreateIndexOnProjectBranchesMeasuresMigrated.TABLE_NAME;
+
+class CreateIndexOnProjectBranchesMeasuresMigratedIT {
+
+ private static final String INDEX_NAME = "pb_measures_migrated";
+
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(CreateIndexOnProjectBranchesMeasuresMigrated.class);
+ private final CreateIndexOnProjectBranchesMeasuresMigrated underTest = new CreateIndexOnProjectBranchesMeasuresMigrated(db.database());
+
+ @Test
+ void migration_should_create_index() throws SQLException {
+ db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+
+ underTest.execute();
+
+ db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+ }
+
+ @Test
+ void migration_should_be_reentrant() throws SQLException {
+ underTest.execute();
+ underTest.execute();
+
+ db.assertIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME);
+ }
+}
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasuresIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasuresIT.java
new file mode 100644
index 00000000000..fda3df82af9
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasuresIT.java
@@ -0,0 +1,184 @@
+/*
+ * 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.v107;
+
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.MigrationDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+
+class MigrateBranchesLiveMeasuresToMeasuresIT {
+
+ private static final String MEASURES_MIGRATED_COLUMN = "measures_migrated";
+ public static final String SELECT_MEASURE = "select component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at " +
+ "from measures where component_uuid = '%s'";
+
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(MigrateBranchesLiveMeasuresToMeasures.class);
+
+ private final SequenceUuidFactory uuidFactory = new SequenceUuidFactory();
+ private final System2 system2 = mock();
+ private final DataChange underTest = new MigrateBranchesLiveMeasuresToMeasures(db.database(), system2);
+
+ @Test
+ void shall_complete_when_tables_are_empty() throws SQLException {
+ underTest.execute();
+
+ assertThat(db.countRowsOfTable("measures")).isZero();
+ }
+
+ @Test
+ void shall_not_migrate_when_branch_is_already_flagged() throws SQLException {
+ String nclocMetricUuid = insertMetric("ncloc", "INT");
+ String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
+ String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
+ String branch1 = "branch_1";
+ insertMigratedBranch(branch1);
+ insertMeasure(branch1, nclocMetricUuid, Map.of("value", 120));
+ insertMeasure(branch1, qgStatusMetricUuid, Map.of("text_value", "ok"));
+ insertMeasure(branch1, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
+
+ insertMigratedBranch("branch_2");
+ insertMeasure("branch_2", nclocMetricUuid, Map.of("value", 14220));
+
+ underTest.execute();
+
+ assertThat(db.countRowsOfTable("measures")).isZero();
+ }
+
+ @Test
+ void should_flag_branch_with_no_measures() throws SQLException {
+ String branch = "branch_3";
+ insertNotMigratedBranch(branch);
+
+ underTest.execute();
+
+ assertBranchMigrated(branch);
+ assertThat(db.countRowsOfTable("measures")).isZero();
+ }
+
+ @Test
+ void should_migrate_branch_with_measures() throws SQLException {
+ String nclocMetricUuid = insertMetric("ncloc", "INT");
+ String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
+ String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
+
+ String branch1 = "branch_4";
+ insertNotMigratedBranch(branch1);
+ String component1 = uuidFactory.create();
+ String component2 = uuidFactory.create();
+ insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120));
+ insertMeasure(branch1, component1, qgStatusMetricUuid, Map.of("text_value", "ok"));
+ insertMeasure(branch1, component2, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
+
+ String branch2 = "branch_5";
+ insertNotMigratedBranch(branch2);
+ insertMeasure(branch2, nclocMetricUuid, Map.of("value", 64));
+
+ String migratedBranch = "branch_6";
+ insertMigratedBranch(migratedBranch);
+ insertMeasure(migratedBranch, nclocMetricUuid, Map.of("value", 3684));
+
+ underTest.execute();
+
+ assertBranchMigrated(branch1);
+ assertBranchMigrated(branch2);
+ assertThat(db.countRowsOfTable("measures")).isEqualTo(3);
+
+ assertThat(db.select(format(SELECT_MEASURE, component1)))
+ .hasSize(1)
+ .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
+ .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0,\"quality_gate_status\":\"ok\"}", 6033012287291512746L));
+
+ assertThat(db.select(format(SELECT_MEASURE, component2)))
+ .hasSize(1)
+ .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
+ .containsOnly(tuple(component2, branch1, "{\"metric_with_data\":\"some data\"}", -4524184678167636687L));
+ }
+
+ private void assertBranchMigrated(String branch) {
+ List<Map<String, Object>> result = db.select(format("select %s as \"MIGRATED\" from project_branches where uuid = '%s'", MEASURES_MIGRATED_COLUMN, branch));
+ assertThat(result)
+ .hasSize(1)
+ .extracting(t -> t.get("MIGRATED"))
+ .containsOnly(true);
+ }
+
+ private String insertMetric(String metricName, String valueType) {
+ String metricUuid = uuidFactory.create();
+ db.executeInsert("metrics",
+ "uuid", metricUuid,
+ "name", metricName,
+ "val_type", valueType);
+ return metricUuid;
+ }
+
+ private void insertMeasure(String branchUuid, String metricUuid, Map<String, Object> data) {
+ insertMeasure(branchUuid, uuidFactory.create(), metricUuid, data);
+ }
+
+ private void insertMeasure(String branchUuid, String componentUuid, String metricUuid, Map<String, Object> data) {
+ Map<String, Object> dataMap = new HashMap<>(data);
+ dataMap.put("uuid", uuidFactory.create());
+ dataMap.put("component_uuid", componentUuid);
+ dataMap.put("project_uuid", branchUuid);
+ dataMap.put("metric_uuid", metricUuid);
+ dataMap.put("created_at", 12L);
+ dataMap.put("updated_at", 12L);
+
+ db.executeInsert("live_measures", dataMap);
+ }
+
+ private void insertNotMigratedBranch(String branchUuid) {
+ insertBranch(branchUuid, false);
+ }
+
+ private void insertMigratedBranch(String branchUuid) {
+ insertBranch(branchUuid, true);
+ }
+
+ private void insertBranch(String branchUuid, boolean migrated) {
+ db.executeInsert("project_branches",
+ "uuid", branchUuid,
+ "kee", branchUuid,
+ "branch_type", "LONG",
+ "project_uuid", uuidFactory.create(),
+ MEASURES_MIGRATED_COLUMN, migrated,
+ "need_issue_sync", false,
+ "is_main", true,
+ "created_at", 12L,
+ "updated_at", 12L
+ );
+ }
+
+
+}
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasuresIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasuresIT.java
new file mode 100644
index 00000000000..50519cde383
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasuresIT.java
@@ -0,0 +1,182 @@
+/*
+ * 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.v107;
+
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.MigrationDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+
+class MigratePortfoliosLiveMeasuresToMeasuresIT {
+
+ private static final String MEASURES_MIGRATED_COLUMN = "measures_migrated";
+ public static final String SELECT_MEASURE = "select component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at " +
+ "from measures where component_uuid = '%s'";
+
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(MigratePortfoliosLiveMeasuresToMeasures.class);
+
+ private final SequenceUuidFactory uuidFactory = new SequenceUuidFactory();
+ private final System2 system2 = mock();
+ private final DataChange underTest = new MigratePortfoliosLiveMeasuresToMeasures(db.database(), system2);
+
+ @Test
+ void shall_complete_when_tables_are_empty() throws SQLException {
+ underTest.execute();
+
+ assertThat(db.countRowsOfTable("measures")).isZero();
+ }
+
+ @Test
+ void shall_not_migrate_when_portfolio_is_already_flagged() throws SQLException {
+ String nclocMetricUuid = insertMetric("ncloc", "INT");
+ String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
+ String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
+ String portfolio1 = "portfolio_1";
+ insertMigratedPortfolio(portfolio1);
+ insertMeasure(portfolio1, nclocMetricUuid, Map.of("value", 120));
+ insertMeasure(portfolio1, qgStatusMetricUuid, Map.of("text_value", "ok"));
+ insertMeasure(portfolio1, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
+
+ insertMigratedPortfolio("portfolio_2");
+ insertMeasure("portfolio_2", nclocMetricUuid, Map.of("value", 14220));
+
+ underTest.execute();
+
+ assertThat(db.countRowsOfTable("measures")).isZero();
+ }
+
+ @Test
+ void should_flag_portfolio_with_no_measures() throws SQLException {
+ String portfolio = "portfolio_3";
+ insertNotMigratedPortfolio(portfolio);
+
+ underTest.execute();
+
+ assertPortfolioMigrated(portfolio);
+ assertThat(db.countRowsOfTable("measures")).isZero();
+ }
+
+ @Test
+ void should_migrate_portfolio_with_measures() throws SQLException {
+ String nclocMetricUuid = insertMetric("ncloc", "INT");
+ String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
+ String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
+
+ String portfolio1 = "portfolio_4";
+ insertNotMigratedPortfolio(portfolio1);
+ String component1 = uuidFactory.create();
+ String component2 = uuidFactory.create();
+ insertMeasure(portfolio1, component1, nclocMetricUuid, Map.of("value", 120));
+ insertMeasure(portfolio1, component1, qgStatusMetricUuid, Map.of("text_value", "ok"));
+ insertMeasure(portfolio1, component2, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
+
+ String portfolio2 = "portfolio_5";
+ insertNotMigratedPortfolio(portfolio2);
+ insertMeasure(portfolio2, nclocMetricUuid, Map.of("value", 64));
+
+ String migratedPortfolio = "portfolio_6";
+ insertMigratedPortfolio(migratedPortfolio);
+ insertMeasure(migratedPortfolio, nclocMetricUuid, Map.of("value", 3684));
+
+ underTest.execute();
+
+ assertPortfolioMigrated(portfolio1);
+ assertPortfolioMigrated(portfolio2);
+ assertThat(db.countRowsOfTable("measures")).isEqualTo(3);
+
+ assertThat(db.select(format(SELECT_MEASURE, component1)))
+ .hasSize(1)
+ .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
+ .containsOnly(tuple(component1, portfolio1, "{\"ncloc\":120.0,\"quality_gate_status\":\"ok\"}", 6033012287291512746L));
+
+ assertThat(db.select(format(SELECT_MEASURE, component2)))
+ .hasSize(1)
+ .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
+ .containsOnly(tuple(component2, portfolio1, "{\"metric_with_data\":\"some data\"}", -4524184678167636687L));
+ }
+
+ private void assertPortfolioMigrated(String portfolio) {
+ List<Map<String, Object>> result = db.select(format("select %s as \"MIGRATED\" from portfolios where uuid = '%s'", MEASURES_MIGRATED_COLUMN, portfolio));
+ assertThat(result)
+ .hasSize(1)
+ .extracting(t -> t.get("MIGRATED"))
+ .containsOnly(true);
+ }
+
+ private String insertMetric(String metricName, String valueType) {
+ String metricUuid = uuidFactory.create();
+ db.executeInsert("metrics",
+ "uuid", metricUuid,
+ "name", metricName,
+ "val_type", valueType);
+ return metricUuid;
+ }
+
+ private void insertMeasure(String portfolioUuid, String metricUuid, Map<String, Object> data) {
+ insertMeasure(portfolioUuid, uuidFactory.create(), metricUuid, data);
+ }
+
+ private void insertMeasure(String portfolioUuid, String componentUuid, String metricUuid, Map<String, Object> data) {
+ Map<String, Object> dataMap = new HashMap<>(data);
+ dataMap.put("uuid", uuidFactory.create());
+ dataMap.put("component_uuid", componentUuid);
+ dataMap.put("project_uuid", portfolioUuid);
+ dataMap.put("metric_uuid", metricUuid);
+ dataMap.put("created_at", 12L);
+ dataMap.put("updated_at", 12L);
+
+ db.executeInsert("live_measures", dataMap);
+ }
+
+ private void insertNotMigratedPortfolio(String portfolioUuid) {
+ insertPortfolio(portfolioUuid, false);
+ }
+
+ private void insertMigratedPortfolio(String portfolioUuid) {
+ insertPortfolio(portfolioUuid, true);
+ }
+
+ private void insertPortfolio(String portfolioUuid, boolean migrated) {
+ db.executeInsert("portfolios",
+ "uuid", portfolioUuid,
+ "kee", portfolioUuid,
+ "name", portfolioUuid,
+ "private", true,
+ "root_uuid", portfolioUuid,
+ "selection_mode", "MANUAL",
+ MEASURES_MIGRATED_COLUMN, migrated,
+ "created_at", 12L,
+ "updated_at", 12L
+ );
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MassUpdate.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MassUpdate.java
index 428c641d98a..a6b25e68a36 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MassUpdate.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MassUpdate.java
@@ -38,7 +38,7 @@ public class MassUpdate {
*
* @return true if the row must be updated, else false. If false, then the update parameter must not be touched.
*/
- boolean handle(Select.Row row, SqlStatement update) throws SQLException;
+ boolean handle(Select.Row row, Upsert update) throws SQLException;
}
@FunctionalInterface
@@ -49,7 +49,7 @@ public class MassUpdate {
* @param updateIndex 0-based
* @return true if the row must be updated, else false. If false, then the update parameter must not be touched.
*/
- boolean handle(Select.Row row, SqlStatement update, int updateIndex) throws SQLException;
+ boolean handle(Select.Row row, Upsert update, int updateIndex) throws SQLException;
}
private final Database db;
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractAddMeasuresMigratedColumnToTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractAddMeasuresMigratedColumnToTable.java
new file mode 100644
index 00000000000..0c3ed45418c
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractAddMeasuresMigratedColumnToTable.java
@@ -0,0 +1,56 @@
+/*
+ * 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.v107;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.def.BooleanColumnDef;
+import org.sonar.server.platform.db.migration.def.ColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AbstractAddMeasuresMigratedColumnToTable extends DdlChange {
+
+ public static final String MIGRATION_FLAG_COLUMN_NAME = "measures_migrated";
+ private final String tableName;
+
+ public AbstractAddMeasuresMigratedColumnToTable(Database db, String tableName) {
+ super(db);
+ this.tableName = tableName;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ try (Connection connection = getDatabase().getDataSource().getConnection()) {
+ if (!DatabaseUtils.tableColumnExists(connection, tableName, MIGRATION_FLAG_COLUMN_NAME)) {
+ ColumnDef columnDef = BooleanColumnDef.newBooleanColumnDefBuilder()
+ .setColumnName(MIGRATION_FLAG_COLUMN_NAME)
+ .setIsNullable(false)
+ .setDefaultValue(false)
+ .build();
+ context.execute(new AddColumnsBuilder(getDialect(), tableName)
+ .addColumn(columnDef)
+ .build());
+ }
+ }
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractCreateIndexOnMeasuresMigrated.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractCreateIndexOnMeasuresMigrated.java
new file mode 100644
index 00000000000..120e395ce8c
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractCreateIndexOnMeasuresMigrated.java
@@ -0,0 +1,53 @@
+/*
+ * 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.v107;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AbstractCreateIndexOnMeasuresMigrated extends DdlChange {
+
+ static final String COLUMN_NAME = "measures_migrated";
+ private final String tableName;
+ private final String indexName;
+
+ public AbstractCreateIndexOnMeasuresMigrated(Database db, String tableName, String indexName) {
+ super(db);
+ this.tableName = tableName;
+ this.indexName = indexName;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ try (Connection connection = getDatabase().getDataSource().getConnection()) {
+ if (!DatabaseUtils.indexExistsIgnoreCase(tableName, indexName, connection)) {
+ context.execute(new CreateIndexBuilder(getDialect())
+ .setTable(tableName)
+ .setName(indexName)
+ .addColumn(COLUMN_NAME, false)
+ .build());
+ }
+ }
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractMigrateLiveMeasuresToMeasures.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractMigrateLiveMeasuresToMeasures.java
new file mode 100644
index 00000000000..cdb53badd79
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AbstractMigrateLiveMeasuresToMeasures.java
@@ -0,0 +1,204 @@
+/*
+ * 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.v107;
+
+import com.google.gson.Gson;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+import org.apache.commons.codec.digest.MurmurHash3;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.Upsert;
+
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public abstract class AbstractMigrateLiveMeasuresToMeasures extends DataChange {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMigrateLiveMeasuresToMeasures.class);
+
+ private static final Set<String> TEXT_VALUE_TYPES = Set.of("STRING", "LEVEL", "DATA", "DISTRIB");
+ private static final Gson GSON = new Gson();
+
+ private static final String SELECT_QUERY = """
+ SELECT lm.component_uuid,
+ m.name,
+ m.val_type,
+ lm.value,
+ lm.text_value,
+ lm.measure_data
+ FROM live_measures lm
+ INNER JOIN metrics m ON m.uuid = lm.metric_uuid
+ WHERE lm.project_uuid = ?
+ ORDER BY lm.component_uuid
+ """;
+
+ private static final String INSERT_QUERY = """
+ insert into measures (component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at)
+ values ( ?, ?, ?, ?, ?, ?)
+ """;
+
+ private final String tableName;
+ private final String item;
+ private final System2 system2;
+
+ protected AbstractMigrateLiveMeasuresToMeasures(Database db, System2 system2, String tableName, String item) {
+ super(db);
+ this.system2 = system2;
+ this.tableName = tableName;
+ this.item = item;
+ }
+
+ private String getSelectUuidQuery() {
+ return format("""
+ SELECT uuid
+ FROM %s
+ WHERE measures_migrated = ?
+ """, tableName);
+ }
+
+ private String getCountQuery() {
+ return format("""
+ SELECT count(uuid)
+ FROM %s
+ """, tableName);
+ }
+
+ private String getUpdateFlagQuery() {
+ return format("""
+ UPDATE %s
+ SET measures_migrated = ?
+ WHERE uuid = ?
+ """, tableName);
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ List<String> uuids = context.prepareSelect(getSelectUuidQuery())
+ .setBoolean(1, false)
+ .list(row -> row.getString(1));
+
+ Long total = context.prepareSelect(getCountQuery())
+ .get(row -> row.getLong(1));
+
+ LOGGER.info("Starting the migration of {} {}s (total number of {}s: {})", uuids.size(), item, item, total);
+ int migrated = 0;
+
+ for (String uuid : uuids) {
+ migrateItem(uuid, context);
+
+ migrated++;
+ if (migrated % 100 == 0) {
+ LOGGER.info("{} {}s migrated", migrated, item);
+ }
+ }
+ }
+
+ private void migrateItem(String uuid, Context context) throws SQLException {
+ LOGGER.debug("Migrating {} {}...", item, uuid);
+
+ Map<String, Object> measureValues = new HashMap<>();
+ AtomicReference<String> componentUuid = new AtomicReference<>(null);
+
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select(SELECT_QUERY).setString(1, uuid);
+ massUpdate.update(INSERT_QUERY);
+ massUpdate.execute((row, update) -> {
+ boolean shouldUpdate = false;
+ String rowComponentUuid = row.getString(1);
+ if (componentUuid.get() == null || !rowComponentUuid.equals(componentUuid.get())) {
+ if (!measureValues.isEmpty()) {
+ preparePersistMeasure(uuid, update, componentUuid, measureValues);
+ shouldUpdate = true;
+ }
+
+ LOGGER.debug("Starting processing of component {}...", rowComponentUuid);
+ componentUuid.set(rowComponentUuid);
+ measureValues.clear();
+ readMeasureValue(row, measureValues);
+ } else {
+ readMeasureValue(row, measureValues);
+ }
+ return shouldUpdate;
+ });
+ // insert the last component
+ if (!measureValues.isEmpty()) {
+ Upsert measureInsert = context.prepareUpsert(INSERT_QUERY);
+ preparePersistMeasure(uuid, measureInsert, componentUuid, measureValues);
+ measureInsert
+ .execute()
+ .commit();
+ }
+
+ LOGGER.debug("Flagging migration done for {} {}...", item, uuid);
+
+ context.prepareUpsert(getUpdateFlagQuery())
+ .setBoolean(1, true)
+ .setString(2, uuid)
+ .execute()
+ .commit();
+
+ LOGGER.debug("Migration finished for {} {}", item, uuid);
+ }
+
+ private void preparePersistMeasure(String uuid, Upsert update, AtomicReference<String> componentUuid, Map<String, Object> measureValues) throws SQLException {
+ LOGGER.debug("Persisting measures for component {}...", componentUuid.get());
+ String jsonValue = GSON.toJson(measureValues);
+
+ long jsonHash = MurmurHash3.hash128(jsonValue.getBytes(UTF_8))[0];
+
+ update.setString(1, componentUuid.get());
+ update.setString(2, uuid);
+ update.setString(3, jsonValue);
+ update.setLong(4, jsonHash);
+ update.setLong(5, system2.now());
+ update.setLong(6, system2.now());
+ }
+
+ private static void readMeasureValue(Select.Row row, Map<String, Object> measureValues) throws SQLException {
+ String metricName = row.getString(2);
+ String valueType = row.getString(3);
+ Double numericValue = row.getDouble(4);
+ String textValue = row.getString(5);
+ byte[] data = row.getBytes(6);
+
+ Object metricValue = getMetricValue(data, textValue, valueType, numericValue);
+ if (metricValue != null) {
+ measureValues.put(metricName, metricValue);
+ }
+ }
+
+ private static Object getMetricValue(@Nullable byte[] data, @Nullable String textValue, String valueType, Double numericValue) {
+ return TEXT_VALUE_TYPES.contains(valueType) ? getTextValue(data, textValue) : numericValue;
+ }
+
+ private static String getTextValue(@Nullable byte[] data, @Nullable String textValue) {
+ return data != null ? new String(data, UTF_8) : textValue;
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTable.java
new file mode 100644
index 00000000000..7a16c70a198
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToPortfoliosTable.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v107;
+
+import org.sonar.db.Database;
+
+public class AddMeasuresMigratedColumnToPortfoliosTable extends AbstractAddMeasuresMigratedColumnToTable {
+
+ static final String PORTFOLIOS_TABLE_NAME = "portfolios";
+
+ public AddMeasuresMigratedColumnToPortfoliosTable(Database db) {
+ super(db, PORTFOLIOS_TABLE_NAME);
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTable.java
new file mode 100644
index 00000000000..fa77640db0e
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/AddMeasuresMigratedColumnToProjectBranchesTable.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v107;
+
+import org.sonar.db.Database;
+
+public class AddMeasuresMigratedColumnToProjectBranchesTable extends AbstractAddMeasuresMigratedColumnToTable {
+
+ public static final String PROJECT_BRANCHES_TABLE_NAME = "project_branches";
+
+ public AddMeasuresMigratedColumnToProjectBranchesTable(Database db) {
+ super(db, PROJECT_BRANCHES_TABLE_NAME);
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigrated.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigrated.java
new file mode 100644
index 00000000000..639c121d131
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnPortfoliosMeasuresMigrated.java
@@ -0,0 +1,32 @@
+/*
+ * 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.v107;
+
+import org.sonar.db.Database;
+
+public class CreateIndexOnPortfoliosMeasuresMigrated extends AbstractCreateIndexOnMeasuresMigrated {
+
+ static final String TABLE_NAME = "portfolios";
+ static final String INDEX_NAME = "portfolios_measures_migrated";
+
+ public CreateIndexOnPortfoliosMeasuresMigrated(Database db) {
+ super(db, TABLE_NAME, INDEX_NAME);
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigrated.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigrated.java
new file mode 100644
index 00000000000..d1a805ab3a5
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/CreateIndexOnProjectBranchesMeasuresMigrated.java
@@ -0,0 +1,32 @@
+/*
+ * 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.v107;
+
+import org.sonar.db.Database;
+
+public class CreateIndexOnProjectBranchesMeasuresMigrated extends AbstractCreateIndexOnMeasuresMigrated {
+
+ static final String TABLE_NAME = "project_branches";
+ static final String INDEX_NAME = "pb_measures_migrated";
+
+ public CreateIndexOnProjectBranchesMeasuresMigrated(Database db) {
+ super(db, TABLE_NAME, INDEX_NAME);
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java
index 1c6bbd95afa..742ecd58643 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java
@@ -58,9 +58,14 @@ public class DbVersion107 implements DbVersion {
.add(10_7_014, "Create 'issues_dependency' table", CreateIssuesDependencyTable.class)
.add(10_7_015, "Add 'ai_code_assurance' column to 'projects' table", AddAiCodeAssuranceColumnInProjectsTable.class)
.add(10_7_016, "Create 'measures' table", CreateMeasuresTable.class)
- // TODO data migration
- .add(10_7_018, "Create primary key on 'measures' table", CreatePrimaryKeyOnMeasuresTable.class)
- .add(10_7_019, "Create index on column 'branch_uuid' in 'measures' table", CreateIndexOnMeasuresTable.class);
+ .add(10_7_017, "Add 'measures_migrated' column on 'project_branches' table", AddMeasuresMigratedColumnToProjectBranchesTable.class)
+ .add(10_7_018, "Create index on 'project_branches.measures_migrated'", CreateIndexOnProjectBranchesMeasuresMigrated.class)
+ .add(10_7_019, "Migrate the content of 'live_measures' to 'measures' for branches", MigrateBranchesLiveMeasuresToMeasures.class)
+ .add(10_7_020, "Add 'measures_migrated' column on 'portfolios' table", AddMeasuresMigratedColumnToPortfoliosTable.class)
+ .add(10_7_021, "Create index on 'portfolios.measures_migrated'", CreateIndexOnPortfoliosMeasuresMigrated.class)
+ .add(10_7_022, "Migrate the content of 'live_measures' to 'measures' for portfolios", MigratePortfoliosLiveMeasuresToMeasures.class)
+ .add(10_7_023, "Create primary key on 'measures' table", CreatePrimaryKeyOnMeasuresTable.class)
+ .add(10_7_024, "Create index on column 'branch_uuid' in 'measures' table", CreateIndexOnMeasuresTable.class);
}
}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasures.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasures.java
new file mode 100644
index 00000000000..135f887ebc9
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateBranchesLiveMeasuresToMeasures.java
@@ -0,0 +1,30 @@
+/*
+ * 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.v107;
+
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+
+public class MigrateBranchesLiveMeasuresToMeasures extends AbstractMigrateLiveMeasuresToMeasures {
+
+ public MigrateBranchesLiveMeasuresToMeasures(Database db, System2 system2) {
+ super(db, system2, "project_branches", "branch");
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasures.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasures.java
new file mode 100644
index 00000000000..9ea66fd6382
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigratePortfoliosLiveMeasuresToMeasures.java
@@ -0,0 +1,30 @@
+/*
+ * 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.v107;
+
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+
+public class MigratePortfoliosLiveMeasuresToMeasures extends AbstractMigrateLiveMeasuresToMeasures {
+
+ protected MigratePortfoliosLiveMeasuresToMeasures(Database db, System2 system2) {
+ super(db, system2, "portfolios", "portfolio");
+ }
+}