diff options
author | Havoc Pennington <hp@pobox.com> | 2025-03-06 15:05:10 -0500 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2025-03-07 20:03:09 +0000 |
commit | bc066d1906129c980e70c57685b2ba91a2e45876 (patch) | |
tree | 1e575ab344442cb7e904103f8e31be098fe92367 | |
parent | 9cee4bb869a2dd367a0898a8769a04b01cdd9c00 (diff) | |
download | sonarqube-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).
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); + } } |