aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-db-dao
diff options
context:
space:
mode:
authorEric Giffon <eric.giffon@sonarsource.com>2024-08-29 14:37:24 +0200
committersonartech <sonartech@sonarsource.com>2024-10-09 20:02:46 +0000
commit264769a69d03fd61c87491be3da5f101469bb60c (patch)
treeac39fd8068d8155ddb93e306fa542651bcb5facd /server/sonar-db-dao
parenta4da413ce8b4eab78aadc9e492d437f45d9b7e6f (diff)
downloadsonarqube-264769a69d03fd61c87491be3da5f101469bb60c.tar.gz
sonarqube-264769a69d03fd61c87491be3da5f101469bb60c.zip
SONAR-22872 CE step to persist measures in JSON format
Diffstat (limited to 'server/sonar-db-dao')
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java7
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDao.java61
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDto.java101
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureHash.java32
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureMapper.java40
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/measure/MeasureMapper.xml56
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDaoTest.java107
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDtoTest.java68
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureHashTest.java54
-rw-r--r--server/sonar-db-dao/src/testFixtures/java/org/sonar/db/measure/MeasureTesting.java18
12 files changed, 548 insertions, 0 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
index f11922bff2a..c0852b111a5 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
@@ -51,6 +51,7 @@ import org.sonar.db.issue.IssueChangeDao;
import org.sonar.db.issue.IssueDao;
import org.sonar.db.issue.IssueFixedDao;
import org.sonar.db.measure.LiveMeasureDao;
+import org.sonar.db.measure.MeasureDao;
import org.sonar.db.measure.ProjectMeasureDao;
import org.sonar.db.metric.MetricDao;
import org.sonar.db.newcodeperiod.NewCodePeriodDao;
@@ -153,6 +154,7 @@ public class DaoModule extends Module {
IssueDao.class,
IssueFixedDao.class,
IssuesDependencyDao.class,
+ MeasureDao.class,
LiveMeasureDao.class,
ProjectMeasureDao.class,
MetricDao.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
index d04aff1df52..200f3547cb4 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
@@ -51,6 +51,7 @@ import org.sonar.db.issue.IssueChangeDao;
import org.sonar.db.issue.IssueDao;
import org.sonar.db.issue.IssueFixedDao;
import org.sonar.db.measure.LiveMeasureDao;
+import org.sonar.db.measure.MeasureDao;
import org.sonar.db.measure.ProjectMeasureDao;
import org.sonar.db.metric.MetricDao;
import org.sonar.db.newcodeperiod.NewCodePeriodDao;
@@ -129,6 +130,7 @@ public class DbClient {
private final SnapshotDao snapshotDao;
private final ComponentDao componentDao;
private final ComponentKeyUpdaterDao componentKeyUpdaterDao;
+ private final MeasureDao measureDao;
private final ProjectMeasureDao projectMeasureDao;
private final UserDao userDao;
private final UserGroupDao userGroupDao;
@@ -225,6 +227,7 @@ public class DbClient {
snapshotDao = getDao(map, SnapshotDao.class);
componentDao = getDao(map, ComponentDao.class);
componentKeyUpdaterDao = getDao(map, ComponentKeyUpdaterDao.class);
+ measureDao = getDao(map, MeasureDao.class);
projectMeasureDao = getDao(map, ProjectMeasureDao.class);
userDao = getDao(map, UserDao.class);
userGroupDao = getDao(map, UserGroupDao.class);
@@ -397,6 +400,10 @@ public class DbClient {
return componentKeyUpdaterDao;
}
+ public MeasureDao measureDao() {
+ return measureDao;
+ }
+
public ProjectMeasureDao projectMeasureDao() {
return projectMeasureDao;
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
index 4191942d8d0..9aafec712ab 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
@@ -89,6 +89,7 @@ import org.sonar.db.issue.NewCodeReferenceIssueDto;
import org.sonar.db.issue.PrIssueDto;
import org.sonar.db.measure.LargestBranchNclocDto;
import org.sonar.db.measure.LiveMeasureMapper;
+import org.sonar.db.measure.MeasureMapper;
import org.sonar.db.measure.ProjectLocDistributionDto;
import org.sonar.db.measure.ProjectMeasureDto;
import org.sonar.db.measure.ProjectMeasureMapper;
@@ -316,6 +317,7 @@ public class MyBatis {
IssueMapper.class,
IssueFixedMapper.class,
IssuesDependencyMapper.class,
+ MeasureMapper.class,
ProjectMeasureMapper.class,
MetricMapper.class,
NewCodePeriodMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDao.java
new file mode 100644
index 00000000000..eebceb810ad
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDao.java
@@ -0,0 +1,61 @@
+/*
+ * 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.db.measure;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+public class MeasureDao implements Dao {
+
+ private final System2 system2;
+
+ public MeasureDao(System2 system2) {
+ this.system2 = system2;
+ }
+
+ public int insert(DbSession dbSession, MeasureDto dto) {
+ return mapper(dbSession).insert(dto, system2.now());
+ }
+
+ public int update(DbSession dbSession, MeasureDto dto) {
+ return mapper(dbSession).update(dto, system2.now());
+ }
+
+ public Optional<MeasureDto> selectMeasure(DbSession dbSession, String componentUuid) {
+ List<MeasureDto> measures = mapper(dbSession).selectByComponentUuids(List.of(componentUuid));
+ if (!measures.isEmpty()) {
+ // component_uuid column is unique. List can't have more than 1 item.
+ return Optional.of(measures.get(0));
+ }
+ return Optional.empty();
+ }
+
+ public Set<MeasureHash> selectBranchMeasureHashes(DbSession dbSession, String branchUuid) {
+ return mapper(dbSession).selectBranchMeasureHashes(branchUuid);
+ }
+
+ private static MeasureMapper mapper(DbSession dbSession) {
+ return dbSession.getMapper(MeasureMapper.class);
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDto.java
new file mode 100644
index 00000000000..8e93bd9a9a5
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureDto.java
@@ -0,0 +1,101 @@
+/*
+ * 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.db.measure;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.commons.codec.digest.MurmurHash3;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class MeasureDto {
+
+ private static final Gson GSON = new Gson();
+
+ private String componentUuid;
+ private String branchUuid;
+ // measures are kept sorted by metric key so that the value hash is consistent
+ private Map<String, Object> metricValues = new TreeMap<>();
+ private Long jsonValueHash;
+
+ public MeasureDto() {
+ // empty constructor
+ }
+
+ public String getComponentUuid() {
+ return componentUuid;
+ }
+
+ public MeasureDto setComponentUuid(String s) {
+ this.componentUuid = s;
+ return this;
+ }
+
+ public String getBranchUuid() {
+ return branchUuid;
+ }
+
+ public MeasureDto setBranchUuid(String s) {
+ this.branchUuid = s;
+ return this;
+ }
+
+ public Map<String, Object> getMetricValues() {
+ return metricValues;
+ }
+
+ public MeasureDto addValue(String metricKey, Object value) {
+ metricValues.put(metricKey, value);
+ return this;
+ }
+
+ // used by MyBatis mapper
+ public String getJsonValue() {
+ return GSON.toJson(metricValues);
+ }
+
+ // used by MyBatis mapper
+ public MeasureDto setJsonValue(String jsonValue) {
+ metricValues = GSON.fromJson(jsonValue, new TypeToken<TreeMap<String, Object>>() {
+ }.getType());
+ return this;
+ }
+
+ public Long getJsonValueHash() {
+ return jsonValueHash;
+ }
+
+ public long computeJsonValueHash() {
+ jsonValueHash = MurmurHash3.hash128(getJsonValue().getBytes(UTF_8))[0];
+ return jsonValueHash;
+ }
+
+ @Override
+ public String toString() {
+ return "MeasureDto{" +
+ "componentUuid='" + componentUuid + '\'' +
+ ", branchUuid='" + branchUuid + '\'' +
+ ", metricValues=" + metricValues +
+ ", jsonValueHash=" + jsonValueHash +
+ '}';
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureHash.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureHash.java
new file mode 100644
index 00000000000..65b73a665ba
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureHash.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.db.measure;
+
+import java.util.Comparator;
+
+public record MeasureHash(String componentUuid, Long jsonValueHash) implements Comparable<MeasureHash> {
+
+ @Override
+ public int compareTo(MeasureHash o) {
+ return Comparator.comparing(MeasureHash::componentUuid)
+ .thenComparing(MeasureHash::jsonValueHash)
+ .compare(this, o);
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureMapper.java
new file mode 100644
index 00000000000..69b4c2fefbe
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/MeasureMapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.db.measure;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.apache.ibatis.annotations.Param;
+
+public interface MeasureMapper {
+
+ int insert(
+ @Param("dto") MeasureDto dto,
+ @Param("now") long now);
+
+ int update(
+ @Param("dto") MeasureDto dto,
+ @Param("now") long now);
+
+ List<MeasureDto> selectByComponentUuids(@Param("componentUuids") Collection<String> componentUuids);
+
+ Set<MeasureHash> selectBranchMeasureHashes(@Param("branchUuid") String branchUuid);
+}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/measure/MeasureMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/measure/MeasureMapper.xml
new file mode 100644
index 00000000000..8fc634a5f1c
--- /dev/null
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/measure/MeasureMapper.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.measure.MeasureMapper">
+
+ <insert id="insert" parameterType="map" useGeneratedKeys="false">
+ insert into measures (
+ component_uuid,
+ branch_uuid,
+ json_value,
+ json_value_hash,
+ created_at,
+ updated_at
+ ) values (
+ #{dto.componentUuid, jdbcType=VARCHAR},
+ #{dto.branchUuid, jdbcType=VARCHAR},
+ #{dto.jsonValue, jdbcType=VARCHAR},
+ #{dto.jsonValueHash, jdbcType=BIGINT},
+ #{now, jdbcType=BIGINT},
+ #{now, jdbcType=BIGINT}
+ )
+ </insert>
+
+ <update id="update" parameterType="map" useGeneratedKeys="false">
+ update measures set
+ json_value = #{dto.jsonValue, jdbcType=VARCHAR},
+ json_value_hash = #{dto.jsonValueHash, jdbcType=BIGINT},
+ updated_at = #{now, jdbcType=BIGINT}
+ where component_uuid = #{dto.componentUuid, jdbcType=VARCHAR}
+ </update>
+
+ <sql id="columns">
+ m.component_uuid as componentUuid,
+ m.branch_uuid as branchUuid,
+ m.json_value as jsonValue,
+ m.json_value_hash as jsonValueHash
+ </sql>
+
+ <select id="selectByComponentUuids" parameterType="map" resultType="org.sonar.db.measure.MeasureDto">
+ select
+ <include refid="columns"/>
+ from measures m
+ where
+ m.component_uuid in
+ <foreach item="componentUuid" collection="componentUuids" open="(" separator="," close=")">
+ #{componentUuid, jdbcType=VARCHAR}
+ </foreach>
+ </select>
+
+ <select id="selectBranchMeasureHashes" resultType="org.sonar.db.measure.MeasureHash">
+ select component_uuid, json_value_hash
+ from measures
+ where branch_uuid = #{branchUuid, jdbcType=VARCHAR}
+ </select>
+
+</mapper>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDaoTest.java
new file mode 100644
index 00000000000..1fdd6d7b9c1
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDaoTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.db.measure;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.measure.MeasureTesting.newMeasure;
+
+class MeasureDaoTest {
+
+ @RegisterExtension
+ public final DbTester db = DbTester.create(System2.INSTANCE);
+
+ private final MeasureDao underTest = db.getDbClient().measureDao();
+
+ @Test
+ void insert_measure() {
+ MeasureDto dto = newMeasure();
+ int count = underTest.insert(db.getSession(), dto);
+ assertThat(count).isEqualTo(1);
+ verifyTableSize(1);
+ verifyPersisted(dto);
+ }
+
+ @Test
+ void update_measure() {
+ MeasureDto dto = newMeasure();
+ underTest.insert(db.getSession(), dto);
+
+ dto.addValue("metric1", "value1");
+ dto.computeJsonValueHash();
+ int count = underTest.update(db.getSession(), dto);
+
+ assertThat(count).isEqualTo(1);
+ verifyTableSize(1);
+ verifyPersisted(dto);
+ }
+
+ @Test
+ void select_measure() {
+ MeasureDto measure1 = newMeasure();
+ MeasureDto measure2 = newMeasure();
+ underTest.insert(db.getSession(), measure1);
+ underTest.insert(db.getSession(), measure2);
+
+ assertThat(underTest.selectMeasure(db.getSession(), measure1.getComponentUuid()))
+ .hasValueSatisfying(selected -> assertThat(selected).usingRecursiveComparison().isEqualTo(measure1));
+ assertThat(underTest.selectMeasure(db.getSession(), "unknown-component")).isEmpty();
+ }
+
+ @Test
+ void select_branch_measure_hashes() {
+ MeasureDto measure1 = new MeasureDto()
+ .setComponentUuid("c1")
+ .setBranchUuid("b1")
+ .addValue("metric1", "value1");
+ MeasureDto measure2 = new MeasureDto()
+ .setComponentUuid("c2")
+ .setBranchUuid("b1")
+ .addValue("metric2", "value2");
+ MeasureDto measure3 = new MeasureDto()
+ .setComponentUuid("c3")
+ .setBranchUuid("b3")
+ .addValue("metric3", "value3");
+ long hash1 = measure1.computeJsonValueHash();
+ long hash2 = measure2.computeJsonValueHash();
+ measure3.computeJsonValueHash();
+
+ underTest.insert(db.getSession(), measure1);
+ underTest.insert(db.getSession(), measure2);
+ underTest.insert(db.getSession(), measure3);
+
+ assertThat(underTest.selectBranchMeasureHashes(db.getSession(), "b1"))
+ .containsOnly(new MeasureHash("c1", hash1), new MeasureHash("c2", hash2));
+ }
+
+ private void verifyTableSize(int expectedSize) {
+ assertThat(db.countRowsOfTable(db.getSession(), "measures")).isEqualTo(expectedSize);
+ }
+
+ private void verifyPersisted(MeasureDto dto) {
+ assertThat(underTest.selectMeasure(db.getSession(), dto.getComponentUuid())).hasValueSatisfying(selected -> {
+ assertThat(selected).usingRecursiveComparison().isEqualTo(dto);
+ });
+ }
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDtoTest.java
new file mode 100644
index 00000000000..9299402942d
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureDtoTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.db.measure;
+
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class MeasureDtoTest {
+
+ @Test
+ void compute_json_value_hash() {
+ MeasureDto measureDto = new MeasureDto();
+ measureDto.setJsonValue("{\"key\":\"value\"}");
+ assertThat(measureDto.getJsonValueHash()).isNull();
+ assertThat(measureDto.computeJsonValueHash()).isEqualTo(2887272982314571750L);
+ assertThat(measureDto.getJsonValueHash()).isEqualTo(2887272982314571750L);
+ }
+
+ @Test
+ void getMetricValues_returns_all_values_ordered() {
+ MeasureDto measureDto = new MeasureDto()
+ .addValue("string-metric", "value")
+ .addValue("int-metric", 1)
+ .addValue("long-metric", 2L);
+ assertThat(measureDto.getMetricValues()).containsExactlyEntriesOf(new TreeMap<>(Map.of(
+ "int-metric", 1,
+ "long-metric", 2L,
+ "string-metric", "value"
+ )));
+ }
+
+ @Test
+ void toString_prints_all_fields() {
+ MeasureDto measureDto = new MeasureDto()
+ .setBranchUuid("branch-uuid")
+ .setComponentUuid("component-uuid")
+ .addValue("int-metric", 12)
+ .addValue("double-metric", 34.5)
+ .addValue("boolean-metric", true)
+ .addValue("string-metric", "value");
+ measureDto.computeJsonValueHash();
+
+ assertThat(measureDto).hasToString("MeasureDto{componentUuid='component-uuid'" +
+ ", branchUuid='branch-uuid'" +
+ ", metricValues={boolean-metric=true, double-metric=34.5, int-metric=12, string-metric=value}" +
+ ", jsonValueHash=-1071134275520515337}");
+ }
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureHashTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureHashTest.java
new file mode 100644
index 00000000000..ff11db17994
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/MeasureHashTest.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.db.measure;
+
+import java.util.List;
+import java.util.TreeSet;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class MeasureHashTest {
+
+ @Test
+ void hashCode_depends_on_both_fields() {
+ MeasureHash measureHash1 = new MeasureHash("component1", 123L);
+ MeasureHash measureHash2 = new MeasureHash("component", 123L);
+ MeasureHash measureHash3 = new MeasureHash("component", 124L);
+
+ assertThat(measureHash1)
+ .doesNotHaveSameHashCodeAs(measureHash2)
+ .doesNotHaveSameHashCodeAs(measureHash3)
+ .isNotEqualTo(measureHash2)
+ .isNotEqualTo(measureHash3);
+ }
+
+ @Test
+ void sort_by_component_and_hash() {
+ MeasureHash measureHash1 = new MeasureHash("A", 1L);
+ MeasureHash measureHash2 = new MeasureHash("A", 2L);
+ MeasureHash measureHash3 = new MeasureHash("B", 1L);
+ MeasureHash measureHash4 = new MeasureHash("B", 2L);
+
+ TreeSet<MeasureHash> set = new TreeSet<>(List.of(measureHash1, measureHash2, measureHash3, measureHash4));
+
+ assertThat(set).containsExactly(measureHash1, measureHash2, measureHash3, measureHash4);
+ }
+}
diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/measure/MeasureTesting.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/measure/MeasureTesting.java
index a01b6d5ba26..89db3b7adc5 100644
--- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/measure/MeasureTesting.java
+++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/measure/MeasureTesting.java
@@ -90,4 +90,22 @@ public class MeasureTesting {
.setData(String.valueOf(cursor++))
.setValue((double) cursor++);
}
+
+ public static MeasureDto newMeasure() {
+ MeasureDto measureDto = new MeasureDto()
+ .setComponentUuid(String.valueOf(cursor++))
+ .setBranchUuid(String.valueOf(cursor++))
+ .addValue("metric" + cursor++, (double) cursor++);
+ measureDto.computeJsonValueHash();
+ return measureDto;
+ }
+
+ public static MeasureDto newMeasure(ComponentDto component, MetricDto metric, Object value) {
+ MeasureDto measureDto = new MeasureDto()
+ .setComponentUuid(component.uuid())
+ .setBranchUuid(component.branchUuid())
+ .addValue(metric.getKey(), value);
+ measureDto.computeJsonValueHash();
+ return measureDto;
+ }
}