aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-db-dao
diff options
context:
space:
mode:
authorHavoc Pennington <hp@pobox.com>2025-02-03 15:49:29 -0500
committersonartech <sonartech@sonarsource.com>2025-02-19 20:03:12 +0000
commitb39b71107d70faa478402a85d5f71ea2e1312a97 (patch)
tree26e1ca9c8f1d1d4f39b109e153e0750b46699f57 /server/sonar-db-dao
parent933fcdba3b22e7048af11385c343eb2a414567fc (diff)
downloadsonarqube-b39b71107d70faa478402a85d5f71ea2e1312a97.tar.gz
sonarqube-b39b71107d70faa478402a85d5f71ea2e1312a97.zip
SQRP-138 Create the sca_dependencies database table
Diffstat (limited to 'server/sonar-db-dao')
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java198
-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/sca/PackageManager.java28
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesDao.java64
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesMapper.java40
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesQuery.java38
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependencyDto.java184
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/sca/package-info.java23
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaDependenciesMapper.xml113
-rw-r--r--server/sonar-db-dao/src/schema/schema-sq.ddl18
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/sca/PackageManagerTest.java34
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaDependencyDtoTest.java45
-rw-r--r--server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java5
-rw-r--r--server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaDependenciesDbTester.java87
16 files changed, 888 insertions, 0 deletions
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java
new file mode 100644
index 00000000000..99d7289dc07
--- /dev/null
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java
@@ -0,0 +1,198 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+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 org.sonar.db.Pagination;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ProjectData;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ScaDependenciesDaoIT {
+
+ private static final String PROJECT_BRANCH_UUID = "branchUuid";
+
+ @RegisterExtension
+ private final DbTester db = DbTester.create(System2.INSTANCE);
+
+ private final ScaDependenciesDao scaDependenciesDao = db.getDbClient().scaDependenciesDao();
+
+ @Test
+ void insert_shouldPersistScaDependencies() {
+ var scaDependencyDto = insertScaDependency();
+
+ List<Map<String, Object>> select = db.select(db.getSession(), "select * from sca_dependencies");
+ assertThat(select).hasSize(1);
+ Map<String, Object> stringObjectMap = select.get(0);
+ assertThat(stringObjectMap).containsExactlyInAnyOrderEntriesOf(
+ Map.ofEntries(
+ Map.entry("uuid", scaDependencyDto.uuid()),
+ Map.entry("component_uuid", scaDependencyDto.componentUuid()),
+ Map.entry("package_url", scaDependencyDto.packageUrl()),
+ Map.entry("package_manager", scaDependencyDto.packageManager().name()),
+ Map.entry("package_name", scaDependencyDto.packageName()),
+ Map.entry("version", scaDependencyDto.version()),
+ Map.entry("direct", scaDependencyDto.direct()),
+ Map.entry("scope", scaDependencyDto.scope()),
+ Map.entry("dependency_file_path", scaDependencyDto.dependencyFilePath()),
+ Map.entry("license_expression", scaDependencyDto.licenseExpression()),
+ Map.entry("known", scaDependencyDto.known()),
+ Map.entry("created_at", scaDependencyDto.createdAt()),
+ Map.entry("updated_at", scaDependencyDto.updatedAt())
+ )
+ );
+ }
+
+ @Test
+ void deleteByUuid_shouldDeleteScaDependencies() {
+ var scaDependencyDto = insertScaDependency();
+
+ List<Map<String, Object>> select = db.select(db.getSession(), "select * from sca_dependencies");
+ assertThat(select).isNotEmpty();
+
+ scaDependenciesDao.deleteByUuid(db.getSession(), scaDependencyDto.uuid());
+
+ select = db.select(db.getSession(), "select * from sca_dependencies");
+ assertThat(select).isEmpty();
+ }
+
+ @Test
+ void selectByQuery_shouldReturnScaDependencies_whenQueryByBranchUuid() {
+ ProjectData projectData = db.components().insertPublicProject();
+ ScaDependencyDto scaDependencyDto = db.getScaDependenciesDbTester().insertScaDependency(projectData.mainBranchUuid());
+
+ ScaDependenciesQuery scaDependenciesQuery = new ScaDependenciesQuery(projectData.mainBranchUuid(), null);
+ List<ScaDependencyDto> results = scaDependenciesDao.selectByQuery(db.getSession(), scaDependenciesQuery, Pagination.all());
+
+ assertThat(results).hasSize(1);
+ assertThat(results.get(0)).usingRecursiveComparison().isEqualTo(scaDependencyDto);
+ }
+
+ @Test
+ void selectByQuery_shouldReturnPaginatedScaDependencies() {
+ ScaDependencyDto scaDependencyDto1 = insertScaDependency("1");
+ ScaDependencyDto scaDependencyDto2 = insertScaDependency("2");
+ ScaDependencyDto scaDependencyDto3 = insertScaDependency("3");
+ ScaDependencyDto scaDependencyDto4 = insertScaDependency("4");
+
+ ScaDependenciesQuery scaDependenciesQuery = new ScaDependenciesQuery(PROJECT_BRANCH_UUID, null);
+ List<ScaDependencyDto> page1Results = scaDependenciesDao.selectByQuery(db.getSession(), scaDependenciesQuery, Pagination.forPage(1).andSize(2));
+ List<ScaDependencyDto> page2Results = scaDependenciesDao.selectByQuery(db.getSession(), scaDependenciesQuery, Pagination.forPage(2).andSize(2));
+
+ assertThat(page1Results).hasSize(2);
+ assertThat(page1Results.get(0)).usingRecursiveComparison().isEqualTo(scaDependencyDto1);
+ assertThat(page1Results.get(1)).usingRecursiveComparison().isEqualTo(scaDependencyDto2);
+ assertThat(page2Results).hasSize(2);
+ assertThat(page2Results.get(0)).usingRecursiveComparison().isEqualTo(scaDependencyDto3);
+ assertThat(page2Results.get(1)).usingRecursiveComparison().isEqualTo(scaDependencyDto4);
+ }
+
+ @Test
+ void selectByQuery_shouldPartiallyMatchLongName_whenQueriedByText() {
+ ScaDependencyDto projectDepSearched = insertScaDependency("sEArched");
+ insertScaDependency("notWanted");
+ ScaDependencyDto projectDepSearchAsWell = insertScaDependency("sEArchedAsWell");
+ insertScaDependency("notwantedeither");
+
+ ScaDependenciesQuery scaDependenciesQuery = new ScaDependenciesQuery(PROJECT_BRANCH_UUID, "long_nameSearCHed");
+ List<ScaDependencyDto> results = scaDependenciesDao.selectByQuery(db.getSession(), scaDependenciesQuery, Pagination.all());
+
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0)).usingRecursiveComparison().isEqualTo(projectDepSearched);
+ assertThat(results.get(1)).usingRecursiveComparison().isEqualTo(projectDepSearchAsWell);
+ }
+
+ @Test
+ void selectByQuery_shouldExactlyMatchKee_whenQueriedByText() {
+ ScaDependencyDto projectDepSearched = insertScaDependency("1", dto -> dto.setKey("keySearched"));
+ insertScaDependency("2", dto -> dto.setKey("KEySearCHed"));
+ insertScaDependency("3", dto -> dto.setKey("some_keySearched"));
+
+ ScaDependenciesQuery scaDependenciesQuery = new ScaDependenciesQuery(PROJECT_BRANCH_UUID, "keySearched");
+ List<ScaDependencyDto> results = scaDependenciesDao.selectByQuery(db.getSession(), scaDependenciesQuery, Pagination.all());
+
+ assertThat(results).hasSize(1);
+ assertThat(results.get(0)).usingRecursiveComparison().isEqualTo(projectDepSearched);
+ }
+
+ @Test
+ void update_shouldUpdateScaDependency() {
+ ScaDependencyDto scaDependencyDto = insertScaDependency();
+ ScaDependencyDto updatedScaDependency =
+ scaDependencyDto.toBuilder().setUpdatedAt(scaDependencyDto.updatedAt() + 1).setVersion("newVersion").build();
+
+ scaDependenciesDao.update(db.getSession(), updatedScaDependency);
+
+ List<Map<String, Object>> select = db.select(db.getSession(), "select * from sca_dependencies");
+ assertThat(select).hasSize(1);
+ Map<String, Object> stringObjectMap = select.get(0);
+ assertThat(stringObjectMap).containsExactlyInAnyOrderEntriesOf(
+ Map.ofEntries(
+ Map.entry("uuid", updatedScaDependency.uuid()),
+ Map.entry("component_uuid", updatedScaDependency.componentUuid()),
+ Map.entry("package_url", updatedScaDependency.packageUrl()),
+ Map.entry("package_manager", updatedScaDependency.packageManager().name()),
+ Map.entry("package_name", updatedScaDependency.packageName()),
+ Map.entry("version", updatedScaDependency.version()),
+ Map.entry("direct", updatedScaDependency.direct()),
+ Map.entry("scope", updatedScaDependency.scope()),
+ Map.entry("dependency_file_path", updatedScaDependency.dependencyFilePath()),
+ Map.entry("license_expression", updatedScaDependency.licenseExpression()),
+ Map.entry("known", updatedScaDependency.known()),
+ Map.entry("created_at", updatedScaDependency.createdAt()),
+ Map.entry("updated_at", updatedScaDependency.updatedAt())
+ )
+ );
+ }
+
+ @Test
+ void countByQuery_shouldReturnTheTotalOfDependencies() {
+ insertScaDependency("sEArched");
+ insertScaDependency("notWanted");
+ insertScaDependency("sEArchedAsWell");
+ db.getScaDependenciesDbTester().insertScaDependency("another_branch_uuid", "searched");
+
+ ScaDependenciesQuery scaDependenciesQuery = new ScaDependenciesQuery(PROJECT_BRANCH_UUID, "long_nameSearCHed");
+
+ assertThat(scaDependenciesDao.countByQuery(db.getSession(), scaDependenciesQuery)).isEqualTo(2);
+ assertThat(scaDependenciesDao.countByQuery(db.getSession(), new ScaDependenciesQuery(PROJECT_BRANCH_UUID, null))).isEqualTo(3);
+ assertThat(scaDependenciesDao.countByQuery(db.getSession(), new ScaDependenciesQuery("another_branch_uuid", null))).isEqualTo(1);
+ }
+
+ private ScaDependencyDto insertScaDependency() {
+ return db.getScaDependenciesDbTester().insertScaDependency(PROJECT_BRANCH_UUID);
+ }
+
+ private ScaDependencyDto insertScaDependency(String suffix) {
+ return insertScaDependency(suffix, null);
+ }
+
+ private ScaDependencyDto insertScaDependency(String suffix, @Nullable Consumer<ComponentDto> dtoPopulator) {
+ return db.getScaDependenciesDbTester().insertScaDependency(PROJECT_BRANCH_UUID, suffix, dtoPopulator);
+ }
+}
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 db5edec1bf1..dcff6ee1d0d 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
@@ -89,6 +89,7 @@ import org.sonar.db.report.ReportSubscriptionDao;
import org.sonar.db.rule.RuleChangeDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
+import org.sonar.db.sca.ScaDependenciesDao;
import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
import org.sonar.db.schemamigration.SchemaMigrationDao;
import org.sonar.db.scim.ScimGroupDao;
@@ -186,6 +187,7 @@ public class DaoModule extends Module {
RuleChangeDao.class,
RuleRepositoryDao.class,
SamlMessageIdDao.class,
+ ScaDependenciesDao.class,
ScannerAnalysisCacheDao.class,
SchemaMigrationDao.class,
ScimGroupDao.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 4ddbe135d14..279a9fdf83b 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
@@ -89,6 +89,7 @@ import org.sonar.db.report.ReportSubscriptionDao;
import org.sonar.db.rule.RuleChangeDao;
import org.sonar.db.rule.RuleDao;
import org.sonar.db.rule.RuleRepositoryDao;
+import org.sonar.db.sca.ScaDependenciesDao;
import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
import org.sonar.db.schemamigration.SchemaMigrationDao;
import org.sonar.db.scim.ScimGroupDao;
@@ -202,6 +203,7 @@ public class DbClient {
private final IssueFixedDao issueFixedDao;
private final TelemetryMetricsSentDao telemetryMetricsSentDao;
private final ProjectDependenciesDao projectDependenciesDao;
+ private final ScaDependenciesDao scaDependenciesDao;
public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) {
this.database = database;
@@ -299,6 +301,7 @@ public class DbClient {
issueFixedDao = getDao(map, IssueFixedDao.class);
telemetryMetricsSentDao = getDao(map, TelemetryMetricsSentDao.class);
projectDependenciesDao = getDao(map, ProjectDependenciesDao.class);
+ scaDependenciesDao = getDao(map, ScaDependenciesDao.class);
}
public DbSession openSession(boolean batch) {
@@ -666,4 +669,8 @@ public class DbClient {
public ProjectDependenciesDao projectDependenciesDao() {
return projectDependenciesDao;
}
+
+ public ScaDependenciesDao scaDependenciesDao() {
+ return scaDependenciesDao;
+ }
}
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 250f51d9729..7f9fac3f962 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
@@ -153,6 +153,7 @@ import org.sonar.db.rule.RuleChangeMapper;
import org.sonar.db.rule.RuleMapper;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.rule.RuleRepositoryMapper;
+import org.sonar.db.sca.ScaDependenciesMapper;
import org.sonar.db.scannercache.ScannerAnalysisCacheMapper;
import org.sonar.db.schemamigration.SchemaMigrationDto;
import org.sonar.db.schemamigration.SchemaMigrationMapper;
@@ -343,6 +344,7 @@ public class MyBatis {
RuleChangeMapper.class,
RuleRepositoryMapper.class,
SamlMessageIdMapper.class,
+ ScaDependenciesMapper.class,
ScannerAnalysisCacheMapper.class,
SchemaMigrationMapper.class,
ScimGroupMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/PackageManager.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/PackageManager.java
new file mode 100644
index 00000000000..50b49bcd185
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/PackageManager.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+/**
+ * These values come from https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst and correspond
+ * to the package manager string used in PURLs.
+ */
+public enum PackageManager {
+ CARGO, COCOAPODS, COMPOSER, CONAN, CONDA, GEM, GOLANG, MAVEN, NPM, NUGET, PYPI
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesDao.java
new file mode 100644
index 00000000000..a2942a438d5
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesDao.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import java.util.List;
+import java.util.Optional;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.Pagination;
+
+public class ScaDependenciesDao implements Dao {
+
+ private static ScaDependenciesMapper mapper(DbSession session) {
+ return session.getMapper(ScaDependenciesMapper.class);
+ }
+
+ public void insert(DbSession session, ScaDependencyDto scaDependencyDto) {
+ mapper(session).insert(scaDependencyDto);
+ }
+
+ public void deleteByUuid(DbSession session, String uuid) {
+ mapper(session).deleteByUuid(uuid);
+ }
+
+ public Optional<ScaDependencyDto> selectByUuid(DbSession dbSession, String uuid) {
+ return Optional.ofNullable(mapper(dbSession).selectByUuid(uuid));
+ }
+
+ /**
+ * Retrieves all dependencies with a specific branch UUID, no other filtering is done by this method.
+ */
+ public List<ScaDependencyDto> selectByBranchUuid(DbSession dbSession, String branchUuid) {
+ return mapper(dbSession).selectByBranchUuid(branchUuid);
+ }
+
+ public List<ScaDependencyDto> selectByQuery(DbSession session, ScaDependenciesQuery scaDependenciesQuery, Pagination pagination) {
+ return mapper(session).selectByQuery(scaDependenciesQuery, pagination);
+ }
+
+ public int countByQuery(DbSession session, ScaDependenciesQuery scaDependenciesQuery) {
+ return mapper(session).countByQuery(scaDependenciesQuery);
+ }
+
+ public void update(DbSession session, ScaDependencyDto scaDependencyDto) {
+ mapper(session).update(scaDependencyDto);
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesMapper.java
new file mode 100644
index 00000000000..16cdc145978
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesMapper.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+import org.sonar.db.Pagination;
+
+public interface ScaDependenciesMapper {
+ void insert(ScaDependencyDto dto);
+
+ void deleteByUuid(String uuid);
+
+ ScaDependencyDto selectByUuid(String uuid);
+
+ List<ScaDependencyDto> selectByBranchUuid(String branchUuid);
+
+ List<ScaDependencyDto> selectByQuery(@Param("query") ScaDependenciesQuery query, @Param("pagination") Pagination pagination);
+
+ void update(ScaDependencyDto dto);
+
+ int countByQuery(@Param("query") ScaDependenciesQuery query);
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesQuery.java
new file mode 100644
index 00000000000..7dde3be94b2
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependenciesQuery.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import java.util.Locale;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static org.sonar.db.DaoUtils.buildLikeValue;
+import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
+
+public record ScaDependenciesQuery(String branchUuid, @Nullable String query) {
+
+ /**
+ * Used by MyBatis mapper
+ */
+ @CheckForNull
+ public String likeQuery() {
+ return query == null ? null : buildLikeValue(query, BEFORE_AND_AFTER).toLowerCase(Locale.ENGLISH);
+ }
+}
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
new file mode 100644
index 00000000000..6d1a87113f1
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/ScaDependencyDto.java
@@ -0,0 +1,184 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Represents a Software Composition Analysis (SCA) dependency, associated with a component.
+ * The component will be a package component nested inside a project branch component.
+ *
+ * @param uuid primary key
+ * @param componentUuid the component the dependency is associated with
+ * @param packageUrl package URL following the PURL specification
+ * @param packageManager package manager e.g. PYPI
+ * @param packageName package name e.g. "urllib3"
+ * @param version package version e.g. "1.25.6"
+ * @param direct is this a direct dependency of the project
+ * @param scope the scope of the dependency e.g. "development"
+ * @param dependencyFilePath path to the file where the dependency was found, preferring the "manifest" to the "lockfile"
+ * @param licenseExpression an SPDX license expression (NOT a single license, can have parens/AND/OR)
+ * @param known is this package and version known to Sonar (if not it be internal, could be malicious, could be from a weird repo)
+ * @param createdAt timestamp of creation
+ * @param updatedAt timestamp of most recent update
+ */
+public record ScaDependencyDto(
+ String uuid,
+ String componentUuid,
+ String packageUrl,
+ PackageManager packageManager,
+ String packageName,
+ String version,
+ boolean direct,
+ String scope,
+ String dependencyFilePath,
+ String licenseExpression,
+ boolean known,
+ long createdAt,
+ long updatedAt) {
+
+ // These need to be in sync with the database but because the db migration module and this module don't
+ // depend on each other, we can't make one just refer to the other.
+ public static final int PACKAGE_URL_MAX_LENGTH = 400;
+ public static final int PACKAGE_MANAGER_MAX_LENGTH = 20;
+ public static final int PACKAGE_NAME_MAX_LENGTH = 400;
+ public static final int VERSION_MAX_LENGTH = 400;
+ public static final int SCOPE_MAX_LENGTH = 100;
+ public static final int DEPENDENCY_FILE_PATH_MAX_LENGTH = 1000;
+ public static final int LICENSE_EXPRESSION_MAX_LENGTH = 400;
+
+ public ScaDependencyDto {
+ // We want these to raise errors and not silently put junk values in the db
+ checkLength(packageUrl, PACKAGE_URL_MAX_LENGTH, "packageUrl");
+ checkLength(packageName, PACKAGE_NAME_MAX_LENGTH, "packageName");
+ checkLength(version, VERSION_MAX_LENGTH, "version");
+ checkLength(scope, SCOPE_MAX_LENGTH, "scope");
+ checkLength(dependencyFilePath, DEPENDENCY_FILE_PATH_MAX_LENGTH, "dependencyFilePath");
+ checkLength(licenseExpression, LICENSE_EXPRESSION_MAX_LENGTH, "licenseExpression");
+ }
+
+ private static void checkLength(String value, int maxLength, String name) {
+ checkArgument(value.length() <= maxLength, "Maximum length of %s is %s: %s", name, maxLength, value);
+ }
+
+ public static class Builder {
+ private String uuid;
+ private String componentUuid;
+ private String packageUrl;
+ private PackageManager packageManager;
+ private String packageName;
+ private String version;
+ private boolean direct;
+ private String scope;
+ private String dependencyFilePath;
+ private String licenseExpression;
+ private boolean known;
+ private long createdAt;
+ private long updatedAt;
+
+ public Builder setUuid(String uuid) {
+ this.uuid = uuid;
+ return this;
+ }
+
+ public Builder setComponentUuid(String componentUuid) {
+ this.componentUuid = componentUuid;
+ return this;
+ }
+
+ public Builder setPackageUrl(String packageUrl) {
+ this.packageUrl = packageUrl;
+ return this;
+ }
+
+ public Builder setPackageManager(PackageManager packageManager) {
+ this.packageManager = packageManager;
+ return this;
+ }
+
+ public Builder setPackageName(String packageName) {
+ this.packageName = packageName;
+ return this;
+ }
+
+ public Builder setVersion(String version) {
+ this.version = version;
+ return this;
+ }
+
+ public Builder setDirect(boolean direct) {
+ this.direct = direct;
+ return this;
+ }
+
+ public Builder setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public Builder setDependencyFilePath(String dependencyFilePath) {
+ this.dependencyFilePath = dependencyFilePath;
+ return this;
+ }
+
+ public Builder setLicenseExpression(String licenseExpression) {
+ this.licenseExpression = licenseExpression;
+ return this;
+ }
+
+ public Builder setKnown(boolean known) {
+ this.known = known;
+ return this;
+ }
+
+ public Builder setCreatedAt(long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Builder setUpdatedAt(long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
+ public ScaDependencyDto build() {
+ return new ScaDependencyDto(
+ uuid, componentUuid, packageUrl, packageManager, packageName, version, direct, scope, dependencyFilePath, licenseExpression, known, createdAt, updatedAt
+ );
+ }
+ }
+
+ public Builder toBuilder() {
+ return new Builder()
+ .setUuid(this.uuid)
+ .setComponentUuid(this.componentUuid)
+ .setPackageUrl(this.packageUrl)
+ .setPackageManager(this.packageManager)
+ .setPackageName(this.packageName)
+ .setVersion(this.version)
+ .setDirect(this.direct)
+ .setScope(this.scope)
+ .setDependencyFilePath(this.dependencyFilePath)
+ .setLicenseExpression(this.licenseExpression)
+ .setKnown(this.known)
+ .setCreatedAt(this.createdAt)
+ .setUpdatedAt(this.updatedAt);
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/sca/package-info.java b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/package-info.java
new file mode 100644
index 00000000000..91273c3d426
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/sca/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.sca;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaDependenciesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaDependenciesMapper.xml
new file mode 100644
index 00000000000..6ec1e6d3948
--- /dev/null
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaDependenciesMapper.xml
@@ -0,0 +1,113 @@
+<?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.sca.ScaDependenciesMapper">
+ <sql id="scaDependenciesColumns">
+ sd.uuid as uuid,
+ sd.component_uuid as componentUuid,
+ sd.package_url as packageUrl,
+ sd.package_manager as packageManager,
+ sd.package_name as packageName,
+ sd.version as version,
+ sd.direct as direct,
+ sd.scope as scope,
+ sd.dependency_file_path as dependencyFilePath,
+ sd.license_expression as licenseExpression,
+ sd.known as known,
+ sd.created_at as createdAt,
+ sd.updated_at as updatedAt
+ </sql>
+
+ <insert id="insert" parameterType="org.sonar.db.sca.ScaDependencyDto" useGeneratedKeys="false">
+ insert into sca_dependencies (
+ uuid,
+ component_uuid,
+ package_url,
+ package_manager,
+ package_name,
+ version,
+ direct,
+ scope,
+ dependency_file_path,
+ license_expression,
+ known,
+ created_at,
+ updated_at
+ ) values (
+ #{uuid,jdbcType=VARCHAR},
+ #{componentUuid,jdbcType=VARCHAR},
+ #{packageUrl,jdbcType=VARCHAR},
+ #{packageManager,jdbcType=VARCHAR},
+ #{packageName,jdbcType=VARCHAR},
+ #{version,jdbcType=VARCHAR},
+ #{direct,jdbcType=BOOLEAN},
+ #{scope,jdbcType=VARCHAR},
+ #{dependencyFilePath,jdbcType=VARCHAR},
+ #{licenseExpression,jdbcType=VARCHAR},
+ #{known,jdbcType=BOOLEAN},
+ #{createdAt,jdbcType=BIGINT},
+ #{updatedAt,jdbcType=BIGINT}
+ )
+ </insert>
+
+ <delete id="deleteByUuid" parameterType="string">
+ delete from sca_dependencies
+ where uuid = #{uuid,jdbcType=VARCHAR}
+ </delete>
+
+ <select id="selectByUuid" parameterType="string" resultType="org.sonar.db.sca.ScaDependencyDto">
+ select <include refid="scaDependenciesColumns"/>
+ from sca_dependencies pd
+ where sd.uuid = #{uuid,jdbcType=VARCHAR}
+ </select>
+
+ <select id="selectByBranchUuid" parameterType="string" resultType="org.sonar.db.sca.ScaDependencyDto">
+ select <include refid="scaDependenciesColumns"/>
+ from sca_dependencies sd
+ inner join components c on sd.component_uuid = c.uuid
+ where c.branch_uuid = #{branchUuid,jdbcType=VARCHAR}
+ </select>
+
+ <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.sca.ScaDependencyDto">
+ select <include refid="scaDependenciesColumns"/>
+ <include refid="sqlSelectByQuery" />
+ ORDER BY c.kee ASC
+ <include refid="org.sonar.db.common.Common.pagination"/>
+ </select>
+
+ <select id="countByQuery" resultType="int">
+ select count(sd.uuid)
+ <include refid="sqlSelectByQuery" />
+ </select>
+
+ <sql id="sqlSelectByQuery">
+ from sca_dependencies sd
+ inner join components c on sd.component_uuid = c.uuid
+ where c.branch_uuid = #{query.branchUuid,jdbcType=VARCHAR}
+ <if test="query.query() != null">
+ AND (
+ c.kee = #{query.query,jdbcType=VARCHAR}
+ OR lower(c.long_name) LIKE #{query.likeQuery} ESCAPE '/'
+ )
+ </if>
+ </sql>
+
+ <update id="update" parameterType="org.sonar.db.sca.ScaDependencyDto" useGeneratedKeys="false">
+ update sca_dependencies
+ set
+ uuid = #{uuid, jdbcType=VARCHAR},
+ component_uuid = #{componentUuid, jdbcType=VARCHAR},
+ package_url = #{packageUrl, jdbcType=VARCHAR},
+ package_manager = #{packageManager, jdbcType=VARCHAR},
+ package_name = #{packageName, jdbcType=VARCHAR},
+ version = #{version, jdbcType=VARCHAR},
+ direct = #{direct, jdbcType=BOOLEAN},
+ scope = #{scope, jdbcType=VARCHAR},
+ dependency_file_path = #{dependencyFilePath, jdbcType=VARCHAR},
+ license_expression = #{licenseExpression, jdbcType=VARCHAR},
+ known = #{known, jdbcType=BOOLEAN},
+ updated_at = #{updatedAt, 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 5e13a691d7a..07b6b866060 100644
--- a/server/sonar-db-dao/src/schema/schema-sq.ddl
+++ b/server/sonar-db-dao/src/schema/schema-sq.ddl
@@ -1032,6 +1032,24 @@ CREATE TABLE "SAML_MESSAGE_IDS"(
ALTER TABLE "SAML_MESSAGE_IDS" ADD CONSTRAINT "PK_SAML_MESSAGE_IDS" PRIMARY KEY("UUID");
CREATE UNIQUE NULLS NOT DISTINCT INDEX "SAML_MESSAGE_IDS_UNIQUE" ON "SAML_MESSAGE_IDS"("MESSAGE_ID" NULLS FIRST);
+CREATE TABLE "SCA_DEPENDENCIES"(
+ "UUID" CHARACTER VARYING(40) NOT NULL,
+ "COMPONENT_UUID" CHARACTER VARYING(40) NOT NULL,
+ "PACKAGE_URL" CHARACTER VARYING(400) NOT NULL,
+ "PACKAGE_MANAGER" CHARACTER VARYING(20) NOT NULL,
+ "PACKAGE_NAME" CHARACTER VARYING(400) NOT NULL,
+ "VERSION" CHARACTER VARYING(400) NOT NULL,
+ "DIRECT" BOOLEAN NOT NULL,
+ "SCOPE" CHARACTER VARYING(100) NOT NULL,
+ "DEPENDENCY_FILE_PATH" CHARACTER VARYING(1000) NOT NULL,
+ "LICENSE_EXPRESSION" CHARACTER VARYING(400) NOT NULL,
+ "KNOWN" BOOLEAN NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "SCA_DEPENDENCIES" ADD CONSTRAINT "PK_SCA_DEPENDENCIES" PRIMARY KEY("UUID");
+CREATE INDEX "SCA_DEPS_COMPONENT_UUID" ON "SCA_DEPENDENCIES"("COMPONENT_UUID" NULLS FIRST);
+
CREATE TABLE "SCANNER_ANALYSIS_CACHE"(
"BRANCH_UUID" CHARACTER VARYING(40) NOT NULL,
"DATA" BINARY LARGE OBJECT NOT NULL
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/sca/PackageManagerTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/PackageManagerTest.java
new file mode 100644
index 00000000000..2ae5705624a
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/PackageManagerTest.java
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class PackageManagerTest {
+
+ @Test
+ void test_namesAreShortEnough() {
+ for (PackageManager packageManager : PackageManager.values()) {
+ assertThat(packageManager.name().length()).isLessThanOrEqualTo(ScaDependencyDto.PACKAGE_MANAGER_MAX_LENGTH);
+ }
+ }
+}
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
new file mode 100644
index 00000000000..96b3df92fe8
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/sca/ScaDependencyDtoTest.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+class ScaDependencyDtoTest {
+
+ @Test
+ void toBuilder_build_shouldRoundTrip() {
+ var scaDependencyDto = new ScaDependencyDto("scaDependencyUuid",
+ "componentUuid",
+ "packageUrl",
+ PackageManager.MAVEN,
+ "foo:bar",
+ "1.0.0",
+ true,
+ "compile",
+ "pom.xml",
+ "BSD-3-Clause",
+ true,
+ 1L,
+ 2L);
+ assertThat(scaDependencyDto).isEqualTo(scaDependencyDto.toBuilder().build());
+ }
+}
diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java
index e8915f097dd..7b0cae590de 100644
--- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java
+++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java
@@ -57,6 +57,7 @@ import org.sonar.db.property.PropertyDbTester;
import org.sonar.db.qualitygate.QualityGateDbTester;
import org.sonar.db.qualityprofile.QualityProfileDbTester;
import org.sonar.db.rule.RuleDbTester;
+import org.sonar.db.sca.ScaDependenciesDbTester;
import org.sonar.db.source.FileSourceTester;
import org.sonar.db.user.UserDbTester;
import org.sonar.db.webhook.WebhookDbTester;
@@ -98,6 +99,7 @@ public class DbTester extends AbstractDbTester<TestDbImpl> implements BeforeEach
private final AuditDbTester auditDbTester;
private final AnticipatedTransitionDbTester anticipatedTransitionDbTester;
private final ProjectDependenciesDbTester projectDependenciesDbTester;
+ private final ScaDependenciesDbTester scaDependenciesDbTester;
private DbTester(UuidFactory uuidFactory, System2 system2, @Nullable String schemaPath, AuditPersister auditPersister, MyBatisConfExtension... confExtensions) {
super(TestDbImpl.create(schemaPath, confExtensions));
@@ -131,6 +133,7 @@ public class DbTester extends AbstractDbTester<TestDbImpl> implements BeforeEach
this.auditDbTester = new AuditDbTester(this);
this.anticipatedTransitionDbTester = new AnticipatedTransitionDbTester(this);
this.projectDependenciesDbTester = new ProjectDependenciesDbTester(this);
+ this.scaDependenciesDbTester = new ScaDependenciesDbTester(this);
}
public static DbTester create() {
@@ -282,6 +285,8 @@ public class DbTester extends AbstractDbTester<TestDbImpl> implements BeforeEach
return projectDependenciesDbTester;
}
+ public ScaDependenciesDbTester getScaDependenciesDbTester() { return scaDependenciesDbTester; }
+
@Override
public void afterEach(ExtensionContext context) throws Exception {
after();
diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaDependenciesDbTester.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaDependenciesDbTester.java
new file mode 100644
index 00000000000..fec30bea3fa
--- /dev/null
+++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/sca/ScaDependenciesDbTester.java
@@ -0,0 +1,87 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.sca;
+
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+public class ScaDependenciesDbTester {
+ private final DbTester db;
+ private final DbClient dbClient;
+
+ public ScaDependenciesDbTester(DbTester db) {
+ this.db = db;
+ this.dbClient = db.getDbClient();
+ }
+
+ public ComponentDto newPackageComponentDto(String branchUuid, String suffix, @Nullable Consumer<ComponentDto> dtoPopulator) {
+ var name = "foo:bar";
+ ComponentDto componentDto = new ComponentDto().setUuid("uuid" + suffix)
+ .setKey("key" + suffix)
+ .setUuidPath("uuidPath" + suffix)
+ .setName(name + suffix)
+ .setLongName("long_name" + suffix)
+ .setBranchUuid(branchUuid);
+
+ if (dtoPopulator != null) {
+ dtoPopulator.accept(componentDto);
+ }
+
+ return componentDto;
+ }
+
+ public ScaDependencyDto newScaDependencyDto(ComponentDto componentDto, String suffix) {
+ return new ScaDependencyDto("scaDependencyUuid" + suffix,
+ componentDto.uuid(),
+ "packageUrl" + suffix,
+ PackageManager.MAVEN,
+ componentDto.name(),
+ "1.0.0",
+ true,
+ "compile",
+ "pom.xml",
+ "BSD-3-Clause",
+ true,
+ 1L,
+ 2L);
+ }
+
+ public ScaDependencyDto insertScaDependency(String branchUuid) {
+ return insertScaDependency(branchUuid, EMPTY, null);
+ }
+
+ public ScaDependencyDto insertScaDependency(String branchUuid, String suffix) {
+ return insertScaDependency(branchUuid, suffix, null);
+ }
+
+ public ScaDependencyDto insertScaDependency(String branchUuid, String suffix, @Nullable Consumer<ComponentDto> dtoPopulator) {
+ var componentDto = newPackageComponentDto(branchUuid, suffix, dtoPopulator);
+
+ db.components().insertComponent(componentDto);
+ var scaDependencyDto = newScaDependencyDto(componentDto, suffix);
+ dbClient.scaDependenciesDao().insert(db.getSession(), scaDependencyDto);
+ return scaDependencyDto;
+ }
+}