aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHavoc Pennington <hp@pobox.com>2025-03-06 15:05:10 -0500
committersonartech <sonartech@sonarsource.com>2025-03-07 20:03:09 +0000
commitbc066d1906129c980e70c57685b2ba91a2e45876 (patch)
tree1e575ab344442cb7e904103f8e31be098fe92367
parent9cee4bb869a2dd367a0898a8769a04b01cdd9c00 (diff)
downloadsonarqube-bc066d1906129c980e70c57685b2ba91a2e45876.tar.gz
sonarqube-bc066d1906129c980e70c57685b2ba91a2e45876.zip
SCA-97 pull the identity concept out of PersistScalStepImpl and into DTOs themselves
This allows it to be more consistent/documented and reusable (and helps us remember to update it when needed).
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependencyDto.java47
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDto.java23
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaReleaseDto.java23
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaDependencyDtoTest.java51
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDtoTest.java18
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaReleaseDtoTest.java26
6 files changed, 187 insertions, 1 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependencyDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependencyDto.java
index 3601b6839d9..2bcf39be00a 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependencyDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependencyDto.java
@@ -94,6 +94,19 @@ public record ScaDependencyDto(
return userDependencyFilePath != null ? userDependencyFilePath : lockfileDependencyFilePath;
}
+ /**
+ * Returns an object whose .equals and .hashCode would match that of another ScaDependencyDto's
+ * identity() if the two ScaDependencyDto would count as duplicates within the sca_dependencies table.
+ * This is different from the DTOs themselves being equal because some fields do not count in
+ * the identity of the row, and can be updated while preserving the identity. The method just
+ * returns Object and not a type, because it exists just to call .equals and .hashCode on.
+ *
+ * @return an object to be used for hashing and comparing ScaDependencyDto instances for identity
+ */
+ public Identity identity() {
+ return new IdentityImpl(this);
+ }
+
public Builder toBuilder() {
return new Builder()
.setUuid(this.uuid)
@@ -107,6 +120,40 @@ public record ScaDependencyDto(
.setUpdatedAt(this.updatedAt);
}
+ public interface Identity {
+ /**
+ * Return a new identity with a different scaReleaseUuid
+ * @param scaReleaseUuid to swap in to the identity
+ * @return an object to be used for hashing and comparing ScaDependencyDto instances for identity
+ */
+ Identity withScaReleaseUuid(String scaReleaseUuid);
+ }
+
+ /** This object has the subset of fields that have to be unique in a ScaDependencyDto,
+ * so if this is the same for two ScaDependencyDto, we can update rather than insert
+ * those ScaDependencyDto. Conceptually, sca_dependencies table could have a unique
+ * constraint on these fields, though in practice it does not.
+ *<p>
+ * This class is private because it is exclusively used for .equals and .hashCode
+ * so nobody cares about it otherwise.
+ *</p>
+ */
+ private record IdentityImpl(String scaReleaseUuid,
+ boolean direct,
+ String scope,
+ String userDependencyFilePath,
+ String lockfileDependencyFilePath) implements Identity {
+
+ IdentityImpl(ScaDependencyDto dto) {
+ this(dto.scaReleaseUuid(), dto.direct(), dto.scope(), dto.userDependencyFilePath(), dto.lockfileDependencyFilePath());
+ }
+
+ @Override
+ public IdentityImpl withScaReleaseUuid(String scaReleaseUuid) {
+ return new IdentityImpl(scaReleaseUuid, direct, scope, userDependencyFilePath, lockfileDependencyFilePath);
+ }
+ }
+
public static class Builder {
private String uuid;
private String scaReleaseUuid;
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDto.java
index 98b1616a690..74f4979b0fa 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaIssueReleaseDto.java
@@ -59,6 +59,29 @@ public record ScaIssueReleaseDto(
.setUpdatedAt(this.updatedAt);
}
+ /**
+ * Returns an object whose .equals and .hashCode would match that of another ScaIssueReleaseDto's
+ * identity() if the two ScaIssueReleaseDto would count as duplicates within the sca_issues_releases
+ * table.
+ * This is different from the DTOs themselves being equal because some fields do not count in
+ * the identity of the row, and can be updated while preserving the identity. The method just
+ * returns Object and not a type, because it exists just to call .equals and .hashCode on.
+ *
+ * @return an object to be used for hashing and comparing ScaReleaseDto instances for identity
+ */
+ public Identity identity() {
+ return new IdentityImpl(this);
+ }
+
+ public interface Identity {
+ }
+
+ private record IdentityImpl(String scaIssueUuid, String scaReleaseUuid) implements Identity {
+ IdentityImpl(ScaIssueReleaseDto dto) {
+ this(dto.scaIssueUuid(), dto.scaReleaseUuid());
+ }
+ }
+
public static class Builder {
private String uuid;
private String scaIssueUuid;
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaReleaseDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaReleaseDto.java
index 9748098af06..6fc469b9053 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaReleaseDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaReleaseDto.java
@@ -82,6 +82,29 @@ public record ScaReleaseDto(
.setUpdatedAt(this.updatedAt);
}
+ /**
+ * Returns an object whose .equals and .hashCode would match that of another ScaReleaseDto's
+ * identity() if the two ScaReleaseDto would count as duplicates within the sca_releases table
+ * (within a single analysis, so ignoring the componentUuid).
+ * This is different from the DTOs themselves being equal because some fields do not count in
+ * the identity of the row, and can be updated while preserving the identity. The method just
+ * returns Object and not a type, because it exists just to call .equals and .hashCode on.
+ *
+ * @return an object to be used for hashing and comparing ScaReleaseDto instances for identity
+ */
+ public Identity identity() {
+ return new IdentityImpl(this);
+ }
+
+ public interface Identity {
+ }
+
+ private record IdentityImpl(String packageUrl) implements Identity {
+ IdentityImpl(ScaReleaseDto dto) {
+ this(dto.packageUrl());
+ }
+ }
+
public static class Builder {
private String uuid;
private String componentUuid;
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaDependencyDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaDependencyDtoTest.java
index bf899451fcb..ac2e97a0d65 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaDependencyDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaDependencyDtoTest.java
@@ -20,6 +20,7 @@
package org.sonar.db.sca;
import java.util.List;
+import javax.annotation.Nullable;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@@ -41,6 +42,54 @@ class ScaDependencyDtoTest {
}
@Test
+ void test_identity_shouldIgnoreUuidAndUpdatableFields() {
+ var scaDependencyDto = new ScaDependencyDto("scaDependencyUuid",
+ "scaReleaseUuid",
+ true,
+ "compile",
+ "some/path",
+ "another/path",
+ List.of(List.of("pkg:npm/IGNORED@1.0.0")),
+ 1L,
+ 2L);
+ var scaDependencyDtoDifferentButSameIdentity = new ScaDependencyDto("differentUuid",
+ "scaReleaseUuid",
+ true,
+ "compile",
+ "some/path",
+ "another/path",
+ List.of(List.of("pkg:npm/DIFFERENT_ALSO_IGNORED@1.0.0")),
+ 42L,
+ 57L);
+ assertThat(scaDependencyDto.identity()).isEqualTo(scaDependencyDtoDifferentButSameIdentity.identity());
+ assertThat(scaDependencyDto).isNotEqualTo(scaDependencyDtoDifferentButSameIdentity);
+ }
+
+ @Test
+ void test_identity_changingScaReleaseUuid() {
+ var scaDependencyDto = new ScaDependencyDto("scaDependencyUuid",
+ "scaReleaseUuid",
+ true,
+ "compile",
+ "some/path",
+ "another/path",
+ List.of(List.of("pkg:npm/IGNORED@1.0.0")),
+ 1L,
+ 2L);
+ var scaDependencyDtoChangedReleaseUuid = new ScaDependencyDto("scaDependencyUuid",
+ "scaReleaseUuidDifferent",
+ true,
+ "compile",
+ "some/path",
+ "another/path",
+ List.of(List.of("pkg:npm/IGNORED@1.0.0")),
+ 1L,
+ 2L);
+ assertThat(scaDependencyDto.identity()).isNotEqualTo(scaDependencyDtoChangedReleaseUuid.identity());
+ assertThat(scaDependencyDto.identity().withScaReleaseUuid("scaReleaseUuidDifferent")).isEqualTo(scaDependencyDtoChangedReleaseUuid.identity());
+ }
+
+ @Test
void test_primaryDependencyFilePath() {
ScaDependencyDto withUserDependencyFilePath = newScaDependencyDto("manifest");
assertThat(withUserDependencyFilePath.primaryDependencyFilePath()).isEqualTo("manifest");
@@ -48,7 +97,7 @@ class ScaDependencyDtoTest {
assertThat(withoutUserDependencyFilePath.primaryDependencyFilePath()).isEqualTo("lockfileDependencyFilePath");
}
- private ScaDependencyDto newScaDependencyDto(String userDependencyFilePath) {
+ private ScaDependencyDto newScaDependencyDto(@Nullable String userDependencyFilePath) {
return new ScaDependencyDto("dependencyUuid",
"scaReleaseUuid",
true,
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDtoTest.java
index 1a6a33a955a..e1df2cfd1c2 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaIssueReleaseDtoTest.java
@@ -35,4 +35,22 @@ class ScaIssueReleaseDtoTest {
2L);
assertThat(scaIssueReleaseDto.toBuilder().build()).isEqualTo(scaIssueReleaseDto);
}
+
+ @Test
+ void test_identity_shouldIgnoreUuidAndUpdatableFields() {
+ var scaIssueReleaseDto = new ScaIssueReleaseDto("sca-issue-release-uuid",
+ "sca-issue-uuid",
+ "sca-release-uuid",
+ ScaSeverity.INFO,
+ 1L,
+ 2L);
+ var scaIssueReleaseDtoDifferentButSameIdentity = new ScaIssueReleaseDto("differentUuid",
+ "sca-issue-uuid",
+ "sca-release-uuid",
+ ScaSeverity.HIGH,
+ 10L,
+ 20L);
+ assertThat(scaIssueReleaseDto.identity()).isEqualTo(scaIssueReleaseDtoDifferentButSameIdentity.identity());
+ assertThat(scaIssueReleaseDto).isNotEqualTo(scaIssueReleaseDtoDifferentButSameIdentity);
+ }
}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaReleaseDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaReleaseDtoTest.java
index 3b493e3c2a7..5883c348ff2 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaReleaseDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaReleaseDtoTest.java
@@ -39,4 +39,30 @@ class ScaReleaseDtoTest {
2L);
assertThat(scaReleaseDto.toBuilder().build()).isEqualTo(scaReleaseDto);
}
+
+ @Test
+ void test_identity_shouldIgnoreUuidAndUpdatableFields() {
+ var scaReleaseDto = new ScaReleaseDto("scaReleaseUuid",
+ "componentUuid",
+ "packageUrl",
+ PackageManager.MAVEN,
+ "foo:bar",
+ "1.0.0",
+ "MIT",
+ true,
+ 1L,
+ 2L);
+ var scaReleaseDtoDifferentButSameIdentity = new ScaReleaseDto("differentUuid",
+ "componentUuidDifferent",
+ "packageUrl",
+ PackageManager.NPM,
+ "foo:bar-different",
+ "2.0.0",
+ "GPL-3.0",
+ false,
+ 10L,
+ 30L);
+ assertThat(scaReleaseDto.identity()).isEqualTo(scaReleaseDtoDifferentButSameIdentity.identity());
+ assertThat(scaReleaseDto).isNotEqualTo(scaReleaseDtoDifferentButSameIdentity);
+ }
}