From: Sébastien Lesaint Date: Fri, 2 Jun 2017 16:40:07 +0000 (+0200) Subject: SONAR-9028 add PurgeDao.deleteNonRootComponents X-Git-Tag: 6.5-M1~107 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=326c71a9fdf3b604b361c355012c274bce9f42fa;p=sonarqube.git SONAR-9028 add PurgeDao.deleteNonRootComponents --- diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java index 7e6c0f1bf6f..87cccc88623 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java @@ -86,7 +86,7 @@ class PurgeCommands { } @VisibleForTesting - protected void deleteAnalyses(List analysisIdUuids) { + void deleteAnalyses(List analysisIdUuids) { List> analysisUuidsPartitions = Lists.partition(IdUuidPairs.uuids(analysisIdUuids), MAX_SNAPSHOTS_PER_QUERY); deleteAnalysisDuplications(analysisUuidsPartitions); @@ -107,7 +107,7 @@ class PurgeCommands { profiler.stop(); } - public void purgeAnalyses(List analysisUuids) { + void purgeAnalyses(List analysisUuids) { List> analysisUuidsPartitions = Lists.partition(IdUuidPairs.uuids(analysisUuids), MAX_SNAPSHOTS_PER_QUERY); deleteAnalysisDuplications(analysisUuidsPartitions); @@ -156,6 +156,24 @@ class PurgeCommands { profiler.stop(); } + void deleteIssues(List componentUuids) { + if (componentUuids.isEmpty()) { + return; + } + + List> uuidPartitions = Lists.partition(componentUuids, MAX_RESOURCES_PER_QUERY); + + profiler.start("deleteIssues (issue_changes)"); + uuidPartitions.forEach(purgeMapper::deleteIssueChangesByComponentUuids); + session.commit(); + profiler.stop(); + + profiler.start("deleteIssues (issues)"); + uuidPartitions.forEach(purgeMapper::deleteIssuesByComponentUuids); + session.commit(); + profiler.stop(); + } + void deleteLinks(String rootUuid) { profiler.start("deleteLinks (project_links)"); purgeMapper.deleteProjectLinksByComponentUuid(rootUuid); @@ -164,6 +182,9 @@ class PurgeCommands { } void deleteByRootAndModulesOrSubviews(List rootAndModulesOrSubviewsIds) { + if (rootAndModulesOrSubviewsIds.isEmpty()) { + return; + } List> idPartitions = Lists.partition(IdUuidPairs.ids(rootAndModulesOrSubviewsIds), MAX_RESOURCES_PER_QUERY); List> uuidsPartitions = Lists.partition(IdUuidPairs.uuids(rootAndModulesOrSubviewsIds), MAX_RESOURCES_PER_QUERY); @@ -185,7 +206,29 @@ class PurgeCommands { profiler.stop(); } - public void deleteComponentMeasures(List analysisUuids, List componentUuids) { + void deleteComponents(List componentUuids) { + if (componentUuids.isEmpty()) { + return; + } + + profiler.start("deleteComponents (projects)"); + Lists.partition(componentUuids, MAX_RESOURCES_PER_QUERY).forEach(purgeMapper::deleteComponentsByUuids); + session.commit(); + profiler.stop(); + } + + void deleteComponentMeasures(List componentUuids) { + if (componentUuids.isEmpty()) { + return; + } + + profiler.start("deleteComponentMeasures (project_measures)"); + Lists.partition(componentUuids, MAX_RESOURCES_PER_QUERY).forEach(purgeMapper::fullDeleteComponentMeasures); + session.commit(); + profiler.stop(); + } + + void deleteComponentMeasures(List analysisUuids, List componentUuids) { if (analysisUuids.isEmpty() || componentUuids.isEmpty()) { return; } @@ -203,28 +246,39 @@ class PurgeCommands { profiler.stop(); } - public void deleteFileSources(String rootUuid) { + void deleteFileSources(List componentUuids) { + if (componentUuids.isEmpty()) { + return; + } + + profiler.start("deleteFileSources (file_sources)"); + Lists.partition(componentUuids, MAX_RESOURCES_PER_QUERY).forEach(purgeMapper::deleteFileSourcesByFileUuid); + session.commit(); + profiler.stop(); + } + + void deleteFileSources(String rootUuid) { profiler.start("deleteFileSources (file_sources)"); purgeMapper.deleteFileSourcesByProjectUuid(rootUuid); session.commit(); profiler.stop(); } - public void deleteCeActivity(String rootUuid) { + void deleteCeActivity(String rootUuid) { profiler.start("deleteCeActivity (ce_activity)"); purgeMapper.deleteCeActivityByProjectUuid(rootUuid); session.commit(); profiler.stop(); } - public void deleteCeQueue(String rootUuid) { + void deleteCeQueue(String rootUuid) { profiler.start("deleteCeQueue (ce_queue)"); purgeMapper.deleteCeQueueByProjectUuid(rootUuid); session.commit(); profiler.stop(); } - public void deleteWebhookDeliveries(String rootUuid) { + void deleteWebhookDeliveries(String rootUuid) { profiler.start("deleteWebhookDeliveries (webhook_deliveries)"); purgeMapper.deleteWebhookDeliveriesByProjectUuid(rootUuid); session.commit(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java index 59b621b7747..a82a5c96cfa 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java @@ -19,11 +19,14 @@ */ package org.sonar.db.purge; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Set; import org.sonar.api.utils.System2; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -45,6 +48,12 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputs; public class PurgeDao implements Dao { private static final Logger LOG = Loggers.get(PurgeDao.class); private static final String[] UNPROCESSED_STATUS = new String[] {"U"}; + private static final ImmutableSet QUALIFIERS_PROJECT_VIEW = ImmutableSet.of("TRK", "VW"); + private static final ImmutableSet QUALIFIERS_MODULE_SUBVIEW = ImmutableSet.of("BRC", "SVW"); + private static final String SCOPE_PROJECT = "PRJ"; + private static final String SCOPE_FILE = "FIL"; + private static final String QUALIFIER_FILE = "FIL"; + private static final String QUALIFIER_UNIT_TEST = "UTS"; private final ComponentDao componentDao; private final System2 system2; @@ -145,14 +154,14 @@ public class PurgeDao implements Dao { return result; } - public PurgeDao deleteProject(DbSession session, String uuid) { + public PurgeDao deleteRootComponent(DbSession session, String uuid) { PurgeProfiler profiler = new PurgeProfiler(); PurgeCommands purgeCommands = new PurgeCommands(session, profiler); - deleteProject(uuid, mapper(session), purgeCommands); + deleteRootComponent(uuid, mapper(session), purgeCommands); return this; } - private static void deleteProject(String rootUuid, PurgeMapper mapper, PurgeCommands commands) { + private static void deleteRootComponent(String rootUuid, PurgeMapper mapper, PurgeCommands commands) { List rootAndModulesOrSubviews = mapper.selectRootAndModulesOrSubviewsByProjectUuid(rootUuid); long rootId = rootAndModulesOrSubviews.stream() .filter(pair -> pair.getUuid().equals(rootUuid)) @@ -171,6 +180,55 @@ public class PurgeDao implements Dao { commands.deleteWebhookDeliveries(rootUuid); } + /** + * Delete the non root components (ie. neither project nor view) from the specified collection of {@link ComponentDto} + * and data from their child tables. + *

+ * This method has no effect when passed an empty collection or only root components. + *

+ */ + public PurgeDao deleteNonRootComponents(DbSession dbSession, Collection components) { + Set nonRootComponents = components.stream().filter(PurgeDao::isNotRoot).collect(MoreCollectors.toSet()); + if (nonRootComponents.isEmpty()) { + return this; + } + + PurgeProfiler profiler = new PurgeProfiler(); + PurgeCommands purgeCommands = new PurgeCommands(dbSession, profiler); + deleteNonRootComponents(nonRootComponents, purgeCommands); + + return this; + } + + private static void deleteNonRootComponents(Set nonRootComponents, PurgeCommands purgeCommands) { + List modulesOrSubviews = nonRootComponents.stream() + .filter(PurgeDao::isModuleOrSubview) + .map(PurgeDao::toIdUuidPair) + .collect(MoreCollectors.toList()); + purgeCommands.deleteByRootAndModulesOrSubviews(modulesOrSubviews); + List nonRootComponentUuids = nonRootComponents.stream().map(ComponentDto::uuid).collect(MoreCollectors.toList(nonRootComponents.size())); + purgeCommands.deleteComponentMeasures(nonRootComponentUuids); + purgeCommands.deleteFileSources(nonRootComponents.stream().filter(PurgeDao::isFile).map(ComponentDto::uuid).collect(MoreCollectors.toList())); + purgeCommands.deleteIssues(nonRootComponentUuids); + purgeCommands.deleteComponents(nonRootComponentUuids); + } + + private static boolean isNotRoot(ComponentDto dto) { + return !(SCOPE_PROJECT.equals(dto.scope()) && QUALIFIERS_PROJECT_VIEW.contains(dto.qualifier())); + } + + private static boolean isModuleOrSubview(ComponentDto dto) { + return SCOPE_PROJECT.equals(dto.scope()) && QUALIFIERS_MODULE_SUBVIEW.contains(dto.qualifier()); + } + + private static boolean isFile(ComponentDto dto) { + return SCOPE_FILE.equals(dto.qualifier()) && ImmutableSet.of(QUALIFIER_FILE, QUALIFIER_UNIT_TEST).contains(dto.qualifier()); + } + + private static IdUuidPair toIdUuidPair(ComponentDto dto) { + return new IdUuidPair(dto.getId(), dto.uuid()); + } + public void deleteAnalyses(DbSession session, PurgeProfiler profiler, List analysisIdUuids) { new PurgeCommands(session, profiler).deleteAnalyses(analysisIdUuids); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java index 6ea23a7994b..ec9b49e737c 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java @@ -40,6 +40,8 @@ public interface PurgeMapper { void deleteAnalysisMeasures(@Param("analysisUuids") List analysisUuids); + void fullDeleteComponentMeasures(@Param("componentUuids") List componentUuids); + void deleteComponentMeasures(@Param("analysisUuids") List analysisUuids, @Param("componentUuids") List componentUuids); List selectMetricIdsWithoutHistoricalData(); @@ -56,6 +58,8 @@ public interface PurgeMapper { void deleteComponentsByProjectUuid(@Param("rootUuid") String rootUuid); + void deleteComponentsByUuids(@Param("componentUuids") List componentUuids); + void deleteGroupRolesByComponentId(@Param("rootId") long rootId); void deleteUserRolesByComponentId(@Param("rootId") long rootId); @@ -72,6 +76,10 @@ public interface PurgeMapper { void deleteIssuesByProjectUuid(@Param("projectUuid") String projectUuid); + void deleteIssueChangesByComponentUuids(@Param("componentUuids") List componentUuids); + + void deleteIssuesByComponentUuids(@Param("componentUuids") List componentUuids); + List selectOldClosedIssueKeys(@Param("projectUuid") String projectUuid, @Nullable @Param("toDate") Long toDate); void deleteIssuesFromKeys(@Param("keys") List keys); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml index 80f7f578784..dee0337d1ad 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml @@ -76,6 +76,15 @@ + + delete from project_measures + where + component_uuid in + + #{componentUuid,jdbcType=VARCHAR} + + + delete from project_measures where @@ -184,6 +193,15 @@ project_uuid = #{rootUuid,jdbcType=VARCHAR} + + delete from projects + where + uuid in + + #{componentUuid,jdbcType=VARCHAR} + + + delete from group_roles where @@ -237,6 +255,54 @@ where project_uuid = #{projectUuid,jdbcType=VARCHAR} + + delete from issue_changes ic + where + exists ( + select + 1 + from issues i + where + i.kee=ic.issue_key + and i.component_uuid in + + #{componentUuid,jdbcType=VARCHAR} + + ) + + + + + delete issue_changes from issue_changes + inner join issues on + issue_changes.issue_key=issues.kee + where + issues.project_uuid in + + #{componentUuid,jdbcType=VARCHAR} + + + + + + delete ic from issue_changes as ic, issues as i + where + ic.issue_key=i.kee + and i.component_uuid in + + #{componentUuid,jdbcType=VARCHAR} + + + + + delete from issues + where + project_uuid in + + #{componentUuid,jdbcType=VARCHAR} + + + delete from file_sources where project_uuid=#{rootProjectUuid,jdbcType=VARCHAR} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java index 4f6052a2ed6..2e196ac87a9 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java @@ -24,6 +24,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang.math.RandomUtils; import org.junit.Rule; import org.junit.Test; @@ -38,14 +41,28 @@ import org.sonar.db.DbTester; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeQueueDto.Status; +import org.sonar.db.component.ComponentDbTester; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; - +import org.sonar.db.issue.IssueDto; +import org.sonar.db.measure.MeasureDto; +import org.sonar.db.measure.custom.CustomMeasureDto; +import org.sonar.db.property.PropertyDto; +import org.sonar.db.rule.RuleTesting; +import org.sonar.db.source.FileSourceDto; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.sonar.db.ce.CeTaskTypes.REPORT; +import static org.sonar.db.component.ComponentTesting.newDirectory; +import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.component.ComponentTesting.newProjectCopy; import static org.sonar.db.webhook.WebhookDbTesting.newWebhookDeliveryDto; import static org.sonar.db.webhook.WebhookDbTesting.selectAllDeliveryUuids; @@ -132,7 +149,7 @@ public class PurgeDaoTest { public void delete_project_and_associated_data() { dbTester.prepareDbUnit(getClass(), "shouldDeleteProject.xml"); - underTest.deleteProject(dbSession, "A"); + underTest.deleteRootComponent(dbSession, "A"); dbSession.commit(); assertThat(dbTester.countRowsOfTable("projects")).isZero(); @@ -153,7 +170,7 @@ public class PurgeDaoTest { insertCeActivity(anotherLivingProject); dbSession.commit(); - underTest.deleteProject(dbSession, projectToBeDeleted.uuid()); + underTest.deleteRootComponent(dbSession, projectToBeDeleted.uuid()); dbSession.commit(); assertThat(dbTester.countRowsOfTable("ce_activity")).isEqualTo(1); @@ -171,7 +188,7 @@ public class PurgeDaoTest { dbClient.ceQueueDao().insert(dbSession, createCeQueue(anotherLivingProject, Status.PENDING)); dbSession.commit(); - underTest.deleteProject(dbSession, projectToBeDeleted.uuid()); + underTest.deleteRootComponent(dbSession, projectToBeDeleted.uuid()); dbSession.commit(); assertThat(dbTester.countRowsOfTable("ce_queue")).isEqualTo(1); @@ -182,7 +199,7 @@ public class PurgeDaoTest { public void delete_view_and_child() { dbTester.prepareDbUnit(getClass(), "view_sub_view_and_tech_project.xml"); - underTest.deleteProject(dbSession, "A"); + underTest.deleteRootComponent(dbSession, "A"); dbSession.commit(); assertThat(dbTester.countSql("select count(1) from projects where uuid='A'")).isZero(); assertThat(dbTester.countRowsOfTable("projects")).isZero(); @@ -195,30 +212,30 @@ public class PurgeDaoTest { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Couldn't find root component with uuid " + module.uuid()); - - underTest.deleteProject(dbSession, module.uuid()); + + underTest.deleteRootComponent(dbSession, module.uuid()); } @Test public void deleteProject_fails_with_IAE_if_specified_component_is_directory() { ComponentDto privateProject = dbTester.components().insertPrivateProject(); - ComponentDto directory = dbTester.components().insertComponent(ComponentTesting.newDirectory(privateProject, "A/B")); + ComponentDto directory = dbTester.components().insertComponent(newDirectory(privateProject, "A/B")); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Couldn't find root component with uuid " + directory.uuid()); - underTest.deleteProject(dbSession, directory.uuid()); + underTest.deleteRootComponent(dbSession, directory.uuid()); } @Test public void deleteProject_fails_with_IAE_if_specified_component_is_file() { ComponentDto privateProject = dbTester.components().insertPrivateProject(); - ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(privateProject)); + ComponentDto file = dbTester.components().insertComponent(newFileDto(privateProject)); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Couldn't find root component with uuid " + file.uuid()); - underTest.deleteProject(dbSession, file.uuid()); + underTest.deleteRootComponent(dbSession, file.uuid()); } @Test @@ -229,7 +246,7 @@ public class PurgeDaoTest { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Couldn't find root component with uuid " + subview.uuid()); - underTest.deleteProject(dbSession, subview.uuid()); + underTest.deleteRootComponent(dbSession, subview.uuid()); } @Test @@ -237,7 +254,7 @@ public class PurgeDaoTest { dbTester.prepareDbUnit(getClass(), "view_sub_view_and_tech_project.xml"); // view - underTest.deleteProject(dbSession, "A"); + underTest.deleteRootComponent(dbSession, "A"); dbSession.commit(); assertThat(dbTester.countSql("select count(1) from projects where uuid='A'")).isZero(); } @@ -276,11 +293,390 @@ public class PurgeDaoTest { dbClient.webhookDeliveryDao().insert(dbSession, newWebhookDeliveryDto().setComponentUuid(project.uuid()).setUuid("D1")); dbClient.webhookDeliveryDao().insert(dbSession, newWebhookDeliveryDto().setComponentUuid("P2").setUuid("D2")); - underTest.deleteProject(dbSession, project.uuid()); + underTest.deleteRootComponent(dbSession, project.uuid()); assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2"); } + @Test + public void deleteNonRootComponents_has_no_effect_when_parameter_is_empty() { + DbSession dbSession = mock(DbSession.class); + + underTest.deleteNonRootComponents(dbSession, Collections.emptyList()); + + verifyZeroInteractions(dbSession); + } + + @Test + public void deleteNonRootComponents_has_no_effect_when_parameter_contains_only_projects_and_or_views() { + ComponentDbTester componentDbTester = dbTester.components(); + + verifyNoEffect(componentDbTester.insertPrivateProject()); + verifyNoEffect(componentDbTester.insertPublicProject()); + verifyNoEffect(componentDbTester.insertView()); + verifyNoEffect(componentDbTester.insertView(), componentDbTester.insertPrivateProject(), componentDbTester.insertPublicProject()); + } + + private void verifyNoEffect(ComponentDto firstRoot, ComponentDto... otherRoots) { + DbSession dbSession = mock(DbSession.class); + + List componentDtos = Stream.concat(Stream.of(firstRoot), Arrays.stream(otherRoots)).collect(Collectors.toList()); + Collections.shuffle(componentDtos); // order of collection must not matter + underTest.deleteNonRootComponents(dbSession, componentDtos); + + verifyZeroInteractions(dbSession); + } + + @Test + public void deleteNonRootComponents_deletes_only_non_root_components_of_a_project_from_table_PROJECTS() { + ComponentDto project = new Random().nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); + ComponentDto module1 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto module2 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(module1)); + ComponentDto dir1 = dbTester.components().insertComponent(newDirectory(module1, "A/B")); + + List components = asList( + project, + module1, + module2, + dir1, + dbTester.components().insertComponent(newDirectory(module2, "A/C")), + dbTester.components().insertComponent(newFileDto(dir1)), + dbTester.components().insertComponent(newFileDto(module2)), + dbTester.components().insertComponent(newFileDto(project))); + Collections.shuffle(components); + + underTest.deleteNonRootComponents(dbSession, components); + + assertThat(getUuidsInTableProjects()) + .containsOnly(project.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_only_non_root_components_of_a_view_from_table_PROJECTS() { + ComponentDto[] projects = { + dbTester.components().insertPrivateProject(), + dbTester.components().insertPrivateProject(), + dbTester.components().insertPrivateProject() + }; + + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview1 = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto subview2 = dbTester.components().insertComponent(ComponentTesting.newSubView(subview1)); + List components = asList( + view, + subview1, + subview2, + dbTester.components().insertComponent(newProjectCopy("a", projects[0], view)), + dbTester.components().insertComponent(newProjectCopy("b", projects[1], subview1)), + dbTester.components().insertComponent(newProjectCopy("c", projects[2], subview2))); + Collections.shuffle(components); + + underTest.deleteNonRootComponents(dbSession, components); + + assertThat(getUuidsInTableProjects()) + .containsOnly(view.uuid(), projects[0].uuid(), projects[1].uuid(), projects[2].uuid()); + } + + private Stream getUuidsInTableProjects() { + return dbTester.select("select uuid as \"UUID\" from projects").stream().map(row -> (String) row.get("UUID")); + } + + @Test + public void deleteNonRootComponents_deletes_only_specified_non_root_components_of_a_project_from_table_PROJECTS() { + ComponentDto project = new Random().nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); + ComponentDto module1 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto module2 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(module1)); + ComponentDto dir1 = dbTester.components().insertComponent(newDirectory(module1, "A/B")); + ComponentDto dir2 = dbTester.components().insertComponent(newDirectory(module2, "A/C")); + ComponentDto file1 = dbTester.components().insertComponent(newFileDto(dir1)); + ComponentDto file2 = dbTester.components().insertComponent(newFileDto(module2)); + ComponentDto file3 = dbTester.components().insertComponent(newFileDto(project)); + + underTest.deleteNonRootComponents(dbSession, singletonList(file3)); + assertThat(getUuidsInTableProjects()) + .containsOnly(project.uuid(), module1.uuid(), module2.uuid(), dir1.uuid(), dir2.uuid(), file1.uuid(), file2.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(module1, dir2, file1)); + assertThat(getUuidsInTableProjects()) + .containsOnly(project.uuid(), module2.uuid(), dir1.uuid(), file2.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_only_specified_non_root_components_of_a_view_from_table_PROJECTS() { + ComponentDto[] projects = { + dbTester.components().insertPrivateProject(), + dbTester.components().insertPrivateProject(), + dbTester.components().insertPrivateProject() + }; + + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview1 = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto subview2 = dbTester.components().insertComponent(ComponentTesting.newSubView(subview1)); + ComponentDto pc1 = dbTester.components().insertComponent(newProjectCopy("a", projects[0], view)); + ComponentDto pc2 = dbTester.components().insertComponent(newProjectCopy("b", projects[1], subview1)); + ComponentDto pc3 = dbTester.components().insertComponent(newProjectCopy("c", projects[2], subview2)); + + underTest.deleteNonRootComponents(dbSession, singletonList(pc3)); + assertThat(getUuidsInTableProjects()) + .containsOnly(view.uuid(), projects[0].uuid(), projects[1].uuid(), projects[2].uuid(), + subview1.uuid(), subview2.uuid(), pc1.uuid(), pc2.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(subview1, pc2)); + assertThat(getUuidsInTableProjects()) + .containsOnly(view.uuid(), projects[0].uuid(), projects[1].uuid(), projects[2].uuid(), subview2.uuid(), pc1.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_measures_of_any_non_root_component_of_a_project() { + ComponentDto project = new Random().nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); + ComponentDto module1 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto dir1 = dbTester.components().insertComponent(newDirectory(module1, "A/B")); + ComponentDto file1 = dbTester.components().insertComponent(newFileDto(dir1)); + insertIssueAndChangesFor(project, module1, dir1, file1); + assertThat(getComponentUuidsOfIssueChanges()).containsOnly(project.uuid(), module1.uuid(), dir1.uuid(), file1.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(file1)); + assertThat(getComponentUuidsOfIssueChanges()) + .containsOnly(project.uuid(), module1.uuid(), dir1.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(module1, dir1)); + assertThat(getComponentUuidsOfIssueChanges()) + .containsOnly(project.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_measures_of_any_non_root_component_of_a_view() { + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto pc = dbTester.components().insertComponent(newProjectCopy("a", dbTester.components().insertPrivateProject(), view)); + insertMeasureFor(view, subview, pc); + assertThat(getComponentUuidsOfMeasures()).containsOnly(view.uuid(), subview.uuid(), pc.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(pc)); + assertThat(getComponentUuidsOfMeasures()) + .containsOnly(view.uuid(), subview.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(subview)); + assertThat(getComponentUuidsOfMeasures()) + .containsOnly(view.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_issues_and_changes_of_any_non_root_component_of_a_project() { + ComponentDto project = new Random().nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); + ComponentDto module1 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto dir1 = dbTester.components().insertComponent(newDirectory(module1, "A/B")); + ComponentDto file1 = dbTester.components().insertComponent(newFileDto(dir1)); + insertIssueAndChangesFor(project, module1, dir1, file1); + assertThat(getComponentUuidsOfIssueChanges()).containsOnly(project.uuid(), module1.uuid(), dir1.uuid(), file1.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(file1)); + assertThat(getComponentUuidsOfIssueChanges()) + .containsOnly(project.uuid(), module1.uuid(), dir1.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(module1, dir1)); + assertThat(getComponentUuidsOfIssueChanges()) + .containsOnly(project.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_issues_and_changes_of_any_non_root_component_of_a_view() { + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto pc = dbTester.components().insertComponent(newProjectCopy("a", dbTester.components().insertPrivateProject(), view)); + insertIssueAndChangesFor(view, subview, pc); + assertThat(getComponentUuidsOfIssueChanges()).containsOnly(view.uuid(), subview.uuid(), pc.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(pc)); + assertThat(getComponentUuidsOfIssueChanges()) + .containsOnly(view.uuid(), subview.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(subview)); + assertThat(getComponentUuidsOfIssueChanges()) + .containsOnly(view.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_file_sources_of_file_component_of_a_project_only() { + ComponentDto project = new Random().nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); + ComponentDto module1 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto dir1 = dbTester.components().insertComponent(newDirectory(module1, "A/B")); + ComponentDto file1 = dbTester.components().insertComponent(newFileDto(dir1)); + ComponentDto file2 = dbTester.components().insertComponent(newFileDto(dir1)); + insertFileSources(project, module1, dir1, file1, file2); + assertThat(getComponentUuidsOfFileSources()).containsOnly(project.uuid(), module1.uuid(), dir1.uuid(), file1.uuid(), file2.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(file1)); + assertThat(getComponentUuidsOfFileSources()) + .containsOnly(project.uuid(), module1.uuid(), dir1.uuid(), file2.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(module1, dir1, file2)); + assertThat(getComponentUuidsOfFileSources()) + .containsOnly(project.uuid(), module1.uuid(), dir1.uuid()); + } + + @Test + public void deleteNonRootComponents_does_not_delete_file_sources_of_non_root_components_of_a_view() { + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto pc1 = dbTester.components().insertComponent(newProjectCopy("a", dbTester.components().insertPrivateProject(), view)); + ComponentDto pc2 = dbTester.components().insertComponent(newProjectCopy("b", dbTester.components().insertPrivateProject(), view)); + insertFileSources(view, subview, pc1, pc2); + assertThat(getComponentUuidsOfFileSources()).containsOnly(view.uuid(), subview.uuid(), pc1.uuid(), pc2.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(pc1)); + assertThat(getComponentUuidsOfFileSources()) + .containsOnly(view.uuid(), subview.uuid(), pc1.uuid(), pc2.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(subview, pc2)); + assertThat(getComponentUuidsOfFileSources()) + .containsOnly(view.uuid(), subview.uuid(), pc1.uuid(), pc2.uuid()); + } + + @Test + public void deleteNonRootComponents_deletes_properties_of_modules_of_a_project() { + ComponentDto project = new Random().nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); + ComponentDto module1 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto module2 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(module1)); + ComponentDto module3 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto dir1 = dbTester.components().insertComponent(newDirectory(module1, "A/B")); + ComponentDto file1 = dbTester.components().insertComponent(newFileDto(dir1)); + insertPropertyFor(project, module1, module2, module3, dir1, file1); + assertThat(getResourceIdOfProperties()).containsOnly(project.getId(), module1.getId(), module2.getId(), module3.getId(), dir1.getId(), file1.getId()); + + underTest.deleteNonRootComponents(dbSession, singletonList(module3)); + assertThat(getResourceIdOfProperties()) + .containsOnly(project.getId(), module1.getId(), module2.getId(), dir1.getId(), file1.getId()); + + underTest.deleteNonRootComponents(dbSession, asList(module1, module2, dir1, file1)); + assertThat(getResourceIdOfProperties()) + .containsOnly(project.getId(), dir1.getId(), file1.getId()); + } + + @Test + public void deleteNonRootComponents_properties_of_subviews_of_a_view() { + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview1 = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto subview2 = dbTester.components().insertComponent(ComponentTesting.newSubView(subview1)); + ComponentDto subview3 = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto pc = dbTester.components().insertComponent(newProjectCopy("a", dbTester.components().insertPrivateProject(), view)); + insertPropertyFor(view, subview1, subview2, subview3, pc); + assertThat(getResourceIdOfProperties()).containsOnly(view.getId(), subview1.getId(), subview2.getId(), subview3.getId(), pc.getId()); + + underTest.deleteNonRootComponents(dbSession, singletonList(subview1)); + assertThat(getResourceIdOfProperties()) + .containsOnly(view.getId(), subview2.getId(), subview3.getId(), pc.getId()); + + underTest.deleteNonRootComponents(dbSession, asList(subview2, subview3, pc)); + assertThat(getResourceIdOfProperties()) + .containsOnly(view.getId(), pc.getId()); + } + + @Test + public void deleteNonRootComponents_deletes_manual_measures_of_modules_of_a_project() { + ComponentDto project = new Random().nextBoolean() ? dbTester.components().insertPublicProject() : dbTester.components().insertPrivateProject(); + ComponentDto module1 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto module2 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(module1)); + ComponentDto module3 = dbTester.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto dir1 = dbTester.components().insertComponent(newDirectory(module1, "A/B")); + ComponentDto file1 = dbTester.components().insertComponent(newFileDto(dir1)); + insertManualMeasureFor(project, module1, module2, module3, dir1, file1); + assertThat(getComponentUuidsOfManualMeasures()).containsOnly(project.uuid(), module1.uuid(), module2.uuid(), module3.uuid(), dir1.uuid(), file1.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(module3)); + assertThat(getComponentUuidsOfManualMeasures()) + .containsOnly(project.uuid(), module1.uuid(), module2.uuid(), dir1.uuid(), file1.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(module1, module2, dir1, file1)); + assertThat(getComponentUuidsOfManualMeasures()) + .containsOnly(project.uuid(), dir1.uuid(), file1.uuid()); + } + + @Test + public void deleteNonRootComponents_manual_measures_of_subviews_of_a_view() { + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview1 = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto subview2 = dbTester.components().insertComponent(ComponentTesting.newSubView(subview1)); + ComponentDto subview3 = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto pc = dbTester.components().insertComponent(newProjectCopy("a", dbTester.components().insertPrivateProject(), view)); + insertManualMeasureFor(view, subview1, subview2, subview3, pc); + assertThat(getComponentUuidsOfManualMeasures()).containsOnly(view.uuid(), subview1.uuid(), subview2.uuid(), subview3.uuid(), pc.uuid()); + + underTest.deleteNonRootComponents(dbSession, singletonList(subview1)); + assertThat(getComponentUuidsOfManualMeasures()) + .containsOnly(view.uuid(), subview2.uuid(), subview3.uuid(), pc.uuid()); + + underTest.deleteNonRootComponents(dbSession, asList(subview2, subview3, pc)); + assertThat(getComponentUuidsOfManualMeasures()) + .containsOnly(view.uuid(), pc.uuid()); + } + + private void insertManualMeasureFor(ComponentDto... componentDtos) { + Arrays.stream(componentDtos).forEach(componentDto -> dbClient.customMeasureDao().insert(dbSession, new CustomMeasureDto() + .setComponentUuid(componentDto.uuid()) + .setMetricId(new Random().nextInt()))); + dbSession.commit(); + } + + private Stream getComponentUuidsOfManualMeasures() { + return dbTester.select("select component_uuid as \"COMPONENT_UUID\" from manual_measures").stream() + .map(row -> (String) row.get("COMPONENT_UUID")); + } + + private Stream getResourceIdOfProperties() { + return dbTester.select("select resource_id as \"ID\" from properties").stream() + .map(row -> (Long) row.get("ID")); + } + + private void insertPropertyFor(ComponentDto... components) { + Stream.of(components).forEach(componentDto -> dbTester.properties().insertProperty(new PropertyDto() + .setKey(randomAlphabetic(3)) + .setValue(randomAlphabetic(3)) + .setResourceId(componentDto.getId()))); + } + + private Stream getComponentUuidsOfMeasures() { + return dbTester.select("select component_uuid as \"COMPONENT_UUID\" from project_measures").stream() + .map(row -> (String) row.get("COMPONENT_UUID")); + } + + private void insertMeasureFor(ComponentDto... components) { + Arrays.stream(components).forEach(componentDto -> dbTester.getDbClient().measureDao().insert(dbSession, new MeasureDto() + .setMetricId(new Random().nextInt()) + .setComponentUuid(componentDto.uuid()) + .setAnalysisUuid(randomAlphabetic(3)))); + dbSession.commit(); + } + + private Stream getComponentUuidsOfFileSources() { + return dbTester.select("select file_uuid as \"COMPONENT_UUID\" from file_sources").stream() + .map(row -> (String) row.get("COMPONENT_UUID")); + } + + private void insertFileSources(ComponentDto... components) { + Arrays.stream(components).forEach(component -> dbTester.getDbClient().fileSourceDao().insert(dbSession, new FileSourceDto() + .setFileUuid(component.uuid()) + .setCreatedAt(new Random().nextInt()) + .setUpdatedAt(new Random().nextInt()) + .setProjectUuid(randomAlphabetic(3)) + .setDataType("SOURCE") + .setBinaryData(new byte[] {0, 1}))); + dbSession.commit(); + } + + private Stream getComponentUuidsOfIssueChanges() { + return dbTester.select("select i.component_uuid as \"COMPONENT_UUID\" from issue_changes ic inner join issues i on i.kee = ic.issue_key").stream() + .map(row -> (String) row.get("COMPONENT_UUID")); + } + + private void insertIssueAndChangesFor(ComponentDto... components) { + Arrays.stream(components).forEach(componentDto -> { + IssueDto issue = dbTester.issues().insert(RuleTesting.newRule(), componentDto, componentDto); + dbTester.issues().insertComment(issue, "foo", "bar"); + }); + dbSession.commit(); + } + private CeQueueDto createCeQueue(ComponentDto component, Status status) { CeQueueDto queueDto = new CeQueueDto(); queueDto.setUuid(Uuids.create()); @@ -317,7 +713,7 @@ public class PurgeDaoTest { } private static PurgeConfiguration newConfigurationWith30Days(System2 system2, String... disabledComponentUuids) { - return new PurgeConfiguration(new IdUuidPair(THE_PROJECT_ID, THE_PROJECT_UUID), new String[0], 30, system2, Arrays.asList(disabledComponentUuids)); + return new PurgeConfiguration(new IdUuidPair(THE_PROJECT_ID, THE_PROJECT_UUID), new String[0], 30, system2, asList(disabledComponentUuids)); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java index 27d0fb8fefc..7dd68335f9a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java @@ -57,7 +57,7 @@ public class ComponentCleanerService { if (hasNotProjectScope(project) || isNotDeletable(project)) { throw new IllegalArgumentException("Only projects can be deleted"); } - dbClient.purgeDao().deleteProject(dbSession, project.uuid()); + dbClient.purgeDao().deleteRootComponent(dbSession, project.uuid()); dbSession.commit(); deleteFromIndices(project.uuid());