From 530977c23387e88f99533f9202c5cf670f26893a Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 30 May 2017 17:40:00 +0200 Subject: [PATCH] SONAR-9328 delete settings & manual measures by project/module and by views/subviews --- .../org/sonar/db/purge/PurgeCommands.java | 24 ++- .../java/org/sonar/db/purge/PurgeDao.java | 10 +- .../java/org/sonar/db/purge/PurgeMapper.java | 5 + .../org/sonar/db/purge/PurgeMapper.xml | 16 ++ .../java/org/sonar/db/purge/PurgeDaoTest.java | 57 ++++-- .../org/sonar/db/purge/PurgeMapperTest.java | 163 ++++++++++++++++++ 6 files changed, 248 insertions(+), 27 deletions(-) create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeMapperTest.java 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 d3ed34b2cdc..1c673d97846 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 @@ -163,26 +163,24 @@ class PurgeCommands { profiler.stop(); } - void deleteComponents(List componentIdUuids) { - List> componentIdPartitions = Lists.partition(IdUuidPairs.ids(componentIdUuids), MAX_RESOURCES_PER_QUERY); - List> componentUuidsPartitions = Lists.partition(IdUuidPairs.uuids(componentIdUuids), MAX_RESOURCES_PER_QUERY); - // Note : do not merge the delete statements into a single loop of resource ids. It's - // voluntarily grouped by tables in order to benefit from JDBC batch mode. - // Batch requests can only relate to the same PreparedStatement. - - // possible missing optimization: filter requests according to resource scope + void deleteByRootAndModulesOrSubviews(List rootAndModulesOrSubviewsIds) { + List> idPartitions = Lists.partition(IdUuidPairs.ids(rootAndModulesOrSubviewsIds), MAX_RESOURCES_PER_QUERY); + List> uuidsPartitions = Lists.partition(IdUuidPairs.uuids(rootAndModulesOrSubviewsIds), MAX_RESOURCES_PER_QUERY); - profiler.start("deleteResourceProperties (properties)"); - componentIdPartitions.forEach(purgeMapper::deleteComponentProperties); + profiler.start("deleteByRootAndModulesOrSubviews (properties)"); + idPartitions.forEach(purgeMapper::deleteComponentProperties); session.commit(); profiler.stop(); - profiler.start("deleteResourceManualMeasures (manual_measures)"); - componentUuidsPartitions.forEach(purgeMapper::deleteComponentManualMeasures); + profiler.start("deleteByRootAndModulesOrSubviews (manual_measures)"); + uuidsPartitions.forEach(purgeMapper::deleteComponentManualMeasures); session.commit(); profiler.stop(); + } - profiler.start("deleteResource (projects)"); + void deleteComponents(List componentIdUuids) { + List> componentUuidsPartitions = Lists.partition(IdUuidPairs.uuids(componentIdUuids), MAX_RESOURCES_PER_QUERY); + profiler.start("deleteComponents (projects)"); componentUuidsPartitions.forEach(purgeMapper::deleteComponents); session.commit(); profiler.stop(); 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 c7ee2fc5e9e..fdeafba33c2 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 @@ -153,16 +153,18 @@ public class PurgeDao implements Dao { } private static void deleteProject(String rootUuid, PurgeMapper mapper, PurgeCommands commands) { - List childrenIds = mapper.selectComponentsByProjectUuid(rootUuid); - long rootId = childrenIds.stream() + List componentsIds = mapper.selectComponentsByProjectUuid(rootUuid); + List rootAndModulesOrSubviews = mapper.selectRootAndModulesOrSubviewsByProjectUuid(rootUuid); + long rootId = rootAndModulesOrSubviews.stream() .filter(pair -> pair.getUuid().equals(rootUuid)) .map(IdUuidPair::getId) .findFirst() - .orElseThrow(() -> new IllegalStateException("Couldn't find component for root uuid " + rootUuid)); + .orElseThrow(() -> new IllegalArgumentException("Couldn't find root component with uuid " + rootUuid)); commands.deletePermissions(rootId); commands.deleteLinks(rootUuid); commands.deleteAnalyses(rootUuid); - commands.deleteComponents(childrenIds); + commands.deleteByRootAndModulesOrSubviews(rootAndModulesOrSubviews); + commands.deleteComponents(componentsIds); commands.deleteIssues(rootUuid); commands.deleteFileSources(rootUuid); commands.deleteCeActivity(rootUuid); 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 71f4d1afc67..e8e65b82cea 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 @@ -32,6 +32,11 @@ public interface PurgeMapper { */ List selectComponentsByProjectUuid(String projectUuid); + /** + * Returns the list of modules/subviews and the view/project for the specified project_uuid. + */ + List selectRootAndModulesOrSubviewsByProjectUuid(@Param("rootUuid") String rootUuid); + void deleteAnalyses(@Param("analysisUuids") List analysisUuids); void deleteAnalysisDuplications(@Param("analysisUuids") List analysisUuids); 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 a2d461853e0..c1926a98446 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 @@ -55,6 +55,22 @@ select id, uuid from projects where project_uuid=#{uuid,jdbcType=VARCHAR} or uuid=#{uuid,jdbcType=VARCHAR} + + delete from project_measures where 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 02ff99161e2..4f6052a2ed6 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 @@ -27,6 +27,7 @@ import java.util.List; import org.apache.commons.lang.math.RandomUtils; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.System2; @@ -57,6 +58,8 @@ public class PurgeDaoTest { @Rule public DbTester dbTester = DbTester.create(system2); + @Rule + public ExpectedException expectedException = ExpectedException.none(); private DbClient dbClient = dbTester.getDbClient(); private DbSession dbSession = dbTester.getSession(); @@ -186,18 +189,52 @@ public class PurgeDaoTest { } @Test - public void delete_view_sub_view_and_tech_project() { - dbTester.prepareDbUnit(getClass(), "view_sub_view_and_tech_project.xml"); + public void deleteProject_fails_with_IAE_if_specified_component_is_module() { + ComponentDto privateProject = dbTester.components().insertPrivateProject(); + ComponentDto module = dbTester.components().insertComponent(ComponentTesting.newModuleDto(privateProject)); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Couldn't find root component with uuid " + module.uuid()); + + underTest.deleteProject(dbSession, module.uuid()); + } - // technical project - underTest.deleteProject(dbSession, "D"); - dbSession.commit(); - assertThat(dbTester.countSql("select count(1) from projects where uuid='D'")).isZero(); + @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")); - // sub view - underTest.deleteProject(dbSession, "B"); - dbSession.commit(); - assertThat(dbTester.countSql("select count(1) from projects where uuid='B'")).isZero(); + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Couldn't find root component with uuid " + directory.uuid()); + + underTest.deleteProject(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)); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Couldn't find root component with uuid " + file.uuid()); + + underTest.deleteProject(dbSession, file.uuid()); + } + + @Test + public void deleteProject_fails_with_IAE_if_specified_component_is_subview() { + ComponentDto view = dbTester.components().insertView(); + ComponentDto subview = dbTester.components().insertComponent(ComponentTesting.newSubView(view)); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Couldn't find root component with uuid " + subview.uuid()); + + underTest.deleteProject(dbSession, subview.uuid()); + } + + @Test + public void delete_view_sub_view_and_tech_project() { + dbTester.prepareDbUnit(getClass(), "view_sub_view_and_tech_project.xml"); // view underTest.deleteProject(dbSession, "A"); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeMapperTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeMapperTest.java new file mode 100644 index 00000000000..f9da4da6e68 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeMapperTest.java @@ -0,0 +1,163 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.purge; + +import java.util.Random; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PurgeMapperTest { + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private DbSession dbSession; + private PurgeMapper purgeMapper; + + @Before + public void setUp() throws Exception { + dbSession = db.getDbClient().openSession(false); + purgeMapper = dbSession.getMapper(PurgeMapper.class); + } + + @After + public void tearDown() throws Exception { + if (dbSession != null) { + dbSession.close(); + } + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_returns_empty_when_table_is_empty() { + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid("foo")).isEmpty(); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_returns_project_with_specified_uuid() { + ComponentDto project = randomPublicOrPrivateProject(); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(project.uuid())) + .extracting(IdUuidPair::getUuid) + .containsOnly(project.uuid()); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_returns_modules_with_specified_project_uuid_and_project() { + ComponentDto project = randomPublicOrPrivateProject(); + ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto module2 = db.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto module3 = db.components().insertComponent(ComponentTesting.newModuleDto(project)); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(project.uuid())) + .extracting(IdUuidPair::getUuid) + .containsOnly(project.uuid(), module1.uuid(), module2.uuid(), module3.uuid()); + } + + private ComponentDto randomPublicOrPrivateProject() { + return new Random().nextBoolean() ? db.components().insertPrivateProject() : db.components().insertPublicProject(); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_returns_view_with_specified_uuid() { + ComponentDto view = db.components().insertView(); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(view.uuid())) + .extracting(IdUuidPair::getUuid) + .containsOnly(view.uuid()); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_returns_subviews_with_specified_project_uuid_and_view() { + ComponentDto view = db.components().insertView(); + ComponentDto subview1 = db.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto subview2 = db.components().insertComponent(ComponentTesting.newSubView(view)); + ComponentDto subview3 = db.components().insertComponent(ComponentTesting.newSubView(view)); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(view.uuid())) + .extracting(IdUuidPair::getUuid) + .containsOnly(view.uuid(), subview1.uuid(), subview2.uuid(), subview3.uuid()); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_does_not_return_project_copy_with_specified_project_uuid() { + ComponentDto privateProject = db.components().insertPrivateProject(); + ComponentDto view = db.components().insertView(); + db.components().insertComponent(ComponentTesting.newProjectCopy("a", view, privateProject)); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(view.uuid())) + .extracting(IdUuidPair::getUuid) + .containsOnly(view.uuid()); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_does_not_return_module_with_specified_uuid() { + ComponentDto privateProject = db.components().insertPrivateProject(); + ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(privateProject)); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(module.uuid())) + .isEmpty(); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_does_not_return_directory_with_specified_uuid() { + ComponentDto privateProject = db.components().insertPrivateProject(); + ComponentDto directory = db.components().insertComponent(ComponentTesting.newDirectory(privateProject, "A/B")); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(directory.uuid())) + .isEmpty(); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_does_not_return_file_with_specified_uuid() { + ComponentDto privateProject = db.components().insertPrivateProject(); + ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(privateProject)); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(file.uuid())) + .isEmpty(); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_does_not_return_subview_with_specified_uuid() { + ComponentDto view = db.components().insertView(); + ComponentDto subview = db.components().insertComponent(ComponentTesting.newSubView(view)); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(subview.uuid())) + .isEmpty(); + } + + @Test + public void selectRootAndModulesOrSubviewsByProjectUuid_does_not_return_technicalCopy_with_specified_uuid() { + ComponentDto privateProject = db.components().insertPrivateProject(); + ComponentDto view = db.components().insertView(); + ComponentDto technicalCopy = db.components().insertComponent(ComponentTesting.newProjectCopy("a", view, privateProject)); + + assertThat(purgeMapper.selectRootAndModulesOrSubviewsByProjectUuid(technicalCopy.uuid())) + .isEmpty(); + } +} -- 2.39.5