]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5804 delete source lines from index while purging and deleting a project
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 3 Dec 2014 14:44:17 +0000 (15:44 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 3 Dec 2014 14:44:29 +0000 (15:44 +0100)
14 files changed:
server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java
server/sonar-server/src/main/java/org/sonar/server/computation/DataCleanerStep.java
server/sonar-server/src/main/java/org/sonar/server/source/index/SourceLineIndexer.java
server/sonar-server/src/test/java/org/sonar/server/computation/DataCleanerStepMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/DataCleanerStepTest.java
server/sonar-server/src/test/java/org/sonar/server/source/index/SourceLineIndexerTest.java
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexerTest/line3.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexerTest/line3_other_project.json [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/computation/dbcleaner/ProjectPurgeTask.java
sonar-core/src/main/java/org/sonar/core/purge/PurgeDao.java
sonar-core/src/main/java/org/sonar/core/purge/PurgeMapper.java
sonar-core/src/main/resources/org/sonar/core/purge/PurgeMapper.xml
sonar-core/src/test/java/org/sonar/core/purge/PurgeDaoTest.java
sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/select_purgeable_file_uuids.xml [new file with mode: 0644]

index b7c655660348a4b5e02b477717d186cf0f2b7fd4..b14b61793d68b3356b2ba53a741cd45fe6363265 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.core.purge.PurgeDao;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.issue.index.IssueAuthorizationIndexer;
 import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.source.index.SourceLineIndexer;
 
 public class ComponentCleanerService implements ServerComponent {
 
@@ -37,12 +38,14 @@ public class ComponentCleanerService implements ServerComponent {
   private final PurgeDao purgeDao;
   private final IssueAuthorizationIndexer issueAuthorizationIndexer;
   private final IssueIndexer issueIndexer;
+  private final SourceLineIndexer sourceLineIndexer;
 
-  public ComponentCleanerService(DbClient dbClient, PurgeDao purgeDao, IssueAuthorizationIndexer issueAuthorizationIndexer, IssueIndexer issueIndexer) {
+  public ComponentCleanerService(DbClient dbClient, PurgeDao purgeDao, IssueAuthorizationIndexer issueAuthorizationIndexer, IssueIndexer issueIndexer, SourceLineIndexer sourceLineIndexer) {
     this.dbClient = dbClient;
     this.purgeDao = purgeDao;
     this.issueAuthorizationIndexer = issueAuthorizationIndexer;
     this.issueIndexer = issueIndexer;
+    this.sourceLineIndexer = sourceLineIndexer;
   }
 
   public void delete(String projectKey) {
@@ -65,6 +68,7 @@ public class ComponentCleanerService implements ServerComponent {
     // optimization : index issues is refreshed once at the end
     issueAuthorizationIndexer.deleteProject(projectUuid, false);
     issueIndexer.deleteProject(projectUuid, true);
+    sourceLineIndexer.deleteByProject(projectUuid);
   }
 
 }
index 86e953a638c3534403deb5a0e3562a8478ceaf7d..8636b0ae74cb740feacc9a582ba408ebdb1f3dc0 100644 (file)
@@ -28,18 +28,23 @@ import org.sonar.core.persistence.DbSession;
 import org.sonar.core.purge.PurgeConfiguration;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.properties.ProjectSettingsFactory;
+import org.sonar.server.source.index.SourceLineIndexer;
+
+import java.util.List;
 
 import static org.sonar.core.purge.PurgeConfiguration.newDefaultPurgeConfiguration;
 
 public class DataCleanerStep implements ComputationStep {
   private final ProjectPurgeTask purgeTask;
   private final IssueIndex issueIndex;
+  private final SourceLineIndexer sourceLineIndexer;
   private final ProjectSettingsFactory projectSettingsFactory;
 
-  public DataCleanerStep(ProjectSettingsFactory projectSettingsFactory, ProjectPurgeTask purgeTask, IssueIndex issueIndex) {
+  public DataCleanerStep(ProjectSettingsFactory projectSettingsFactory, ProjectPurgeTask purgeTask, IssueIndex issueIndex, SourceLineIndexer sourceLineIndexer) {
     this.projectSettingsFactory = projectSettingsFactory;
     this.purgeTask = purgeTask;
     this.issueIndex = issueIndex;
+    this.sourceLineIndexer = sourceLineIndexer;
   }
 
   @Override
@@ -49,10 +54,12 @@ public class DataCleanerStep implements ComputationStep {
     Settings settings = projectSettingsFactory.newProjectSettings(session, projectId);
     PurgeConfiguration purgeConfiguration = newDefaultPurgeConfiguration(settings, projectId);
 
+    List<String> fileUuidsToDisable = purgeTask.findUuidsToDisable(session, projectId);
     purgeTask.purge(session, purgeConfiguration, settings);
 
     if (purgeConfiguration.maxLiveDateOfClosedIssues() != null) {
       issueIndex.deleteClosedIssuesOfProjectBefore(project.uuid(), purgeConfiguration.maxLiveDateOfClosedIssues());
+      sourceLineIndexer.deleteByFiles(fileUuidsToDisable);
     }
   }
 
index 12213981ee69905dd4c746ac555c14ffe4bdc9c1..6809c2fd263f1ec588e491ebe2d85f0e27bcc6fb 100644 (file)
@@ -19,8 +19,8 @@
  */
 package org.sonar.server.source.index;
 
-import com.google.common.annotations.VisibleForTesting;
 import org.elasticsearch.action.update.UpdateRequest;
+import org.elasticsearch.index.query.FilterBuilders;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.server.db.DbClient;
@@ -30,6 +30,10 @@ import org.sonar.server.es.EsClient;
 
 import java.sql.Connection;
 import java.util.Iterator;
+import java.util.List;
+
+import static org.sonar.server.source.index.SourceLineIndexDefinition.FIELD_FILE_UUID;
+import static org.sonar.server.source.index.SourceLineIndexDefinition.FIELD_PROJECT_UUID;
 
 public class SourceLineIndexer extends BaseIndexer {
 
@@ -93,8 +97,22 @@ public class SourceLineIndexer extends BaseIndexer {
     esClient.prepareDeleteByQuery(SourceLineIndexDefinition.INDEX)
       .setTypes(SourceLineIndexDefinition.TYPE)
       .setQuery(QueryBuilders.boolQuery()
-        .must(QueryBuilders.termQuery(SourceLineIndexDefinition.FIELD_FILE_UUID, fileUuid))
+        .must(QueryBuilders.termQuery(FIELD_FILE_UUID, fileUuid))
         .must(QueryBuilders.rangeQuery(SourceLineIndexDefinition.FIELD_LINE).gt(lastLine))
       ).get();
   }
+
+  public void deleteByFiles(List<String> uuids) {
+    esClient.prepareDeleteByQuery(SourceLineIndexDefinition.INDEX)
+      .setTypes(SourceLineIndexDefinition.TYPE)
+      .setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), FilterBuilders.termsFilter(FIELD_FILE_UUID, uuids).cache(false)))
+      .get();
+  }
+
+  public void deleteByProject(String projectUuid) {
+    esClient.prepareDeleteByQuery(SourceLineIndexDefinition.INDEX)
+      .setTypes(SourceLineIndexDefinition.TYPE)
+      .setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), FilterBuilders.termFilter(FIELD_PROJECT_UUID, projectUuid).cache(false)))
+      .get();
+  }
 }
index 23e886589b07471eb7890a3119cace32d3730926..7e6cfbefa719cdf464b5ac3910496970f42af763 100644 (file)
@@ -40,6 +40,7 @@ import org.sonar.server.db.DbClient;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.properties.ProjectSettingsFactory;
 import org.sonar.server.search.IndexClient;
+import org.sonar.server.source.index.SourceLineIndexer;
 import org.sonar.server.tester.ServerTester;
 
 import java.util.Date;
@@ -55,6 +56,7 @@ public class DataCleanerStepMediumTest {
   private DbClient dbClient;
   private DbSession dbSession;
   private IndexClient indexClient;
+  private SourceLineIndexer sourceLineIndexer;
   private ProjectSettingsFactory projectSettingsFactory;
   private ProjectPurgeTask purgeTask;
 
@@ -66,8 +68,9 @@ public class DataCleanerStepMediumTest {
     this.indexClient = tester.get(IndexClient.class);
     this.projectSettingsFactory = tester.get(ProjectSettingsFactory.class);
     this.purgeTask = tester.get(ProjectPurgeTask.class);
+    this.sourceLineIndexer = tester.get(SourceLineIndexer.class);
 
-    this.sut = new DataCleanerStep(projectSettingsFactory, purgeTask, indexClient.get(IssueIndex.class));
+    this.sut = new DataCleanerStep(projectSettingsFactory, purgeTask, indexClient.get(IssueIndex.class), sourceLineIndexer);
   }
 
   @After
index ad016a66ec1aa93df5298177082243ee65f74cbc..3c6523d6d9545da3f8b34771829c023e15395cd8 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.core.purge.PurgeConfiguration;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.properties.ProjectSettings;
 import org.sonar.server.properties.ProjectSettingsFactory;
+import org.sonar.server.source.index.SourceLineIndexer;
 
 import java.util.Date;
 
@@ -44,6 +45,7 @@ public class DataCleanerStepTest {
   private DataCleanerStep sut;
   private ProjectPurgeTask purgeTask;
   private IssueIndex issueIndex;
+  private SourceLineIndexer sourceLineIndexer;
   private Settings settings;
   private ProjectSettingsFactory projectSettingsFactory;
 
@@ -51,12 +53,13 @@ public class DataCleanerStepTest {
   public void before() {
     this.purgeTask = mock(ProjectPurgeTask.class);
     this.issueIndex = mock(IssueIndex.class);
+    this.sourceLineIndexer = mock(SourceLineIndexer.class);
     this.settings = mock(ProjectSettings.class);
     this.projectSettingsFactory = mock(ProjectSettingsFactory.class);
     when(projectSettingsFactory.newProjectSettings(any(DbSession.class), anyLong())).thenReturn(settings);
     when(settings.getInt(any(String.class))).thenReturn(123);
 
-    this.sut = new DataCleanerStep(projectSettingsFactory, purgeTask, issueIndex);
+    this.sut = new DataCleanerStep(projectSettingsFactory, purgeTask, issueIndex, sourceLineIndexer);
   }
 
   @Test
index db7f9b5370186c97fcc69e421ff66f5361adb18e..d0731a067345c8d3c133eed246126301c515aea2 100644 (file)
@@ -22,9 +22,13 @@ package org.sonar.server.source.index;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
 import org.apache.commons.io.IOUtils;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
 import org.fest.assertions.MapAssert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -43,6 +47,7 @@ import java.util.List;
 import java.util.Map;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.sonar.server.source.index.SourceLineIndexDefinition.*;
 
 public class SourceLineIndexerTest {
 
@@ -68,24 +73,24 @@ public class SourceLineIndexerTest {
 
   @Test
   public void update_already_indexed_lines() throws Exception {
-    es.client().prepareIndex(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE)
+    prepareIndex()
       .setSource(IOUtils.toString(new FileInputStream(TestUtils.getResource(this.getClass(), "line2.json"))))
       .get();
-    es.client().prepareIndex(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE)
+    prepareIndex()
       .setSource(IOUtils.toString(new FileInputStream(TestUtils.getResource(this.getClass(), "line2_other_file.json"))))
       .setRefresh(true)
       .get();
 
     List<Integer> duplications = ImmutableList.of(1, 2, 3);
     SourceLineDoc line1 = new SourceLineDoc(ImmutableMap.<String, Object>builder()
-      .put(SourceLineIndexDefinition.FIELD_PROJECT_UUID, "abcd")
-      .put(SourceLineIndexDefinition.FIELD_FILE_UUID, "efgh")
-      .put(SourceLineIndexDefinition.FIELD_LINE, 1)
-      .put(SourceLineIndexDefinition.FIELD_SCM_REVISION, "cafebabe")
-      .put(SourceLineIndexDefinition.FIELD_SCM_DATE, DateUtils.parseDateTime("2014-01-01T12:34:56+0100"))
-      .put(SourceLineIndexDefinition.FIELD_SCM_AUTHOR, "polop")
-      .put(SourceLineIndexDefinition.FIELD_SOURCE, "package org.sonar.server.source;")
-      .put(SourceLineIndexDefinition.FIELD_DUPLICATIONS, duplications)
+      .put(FIELD_PROJECT_UUID, "abcd")
+      .put(FIELD_FILE_UUID, "efgh")
+      .put(FIELD_LINE, 1)
+      .put(FIELD_SCM_REVISION, "cafebabe")
+      .put(FIELD_SCM_DATE, DateUtils.parseDateTime("2014-01-01T12:34:56+0100"))
+      .put(FIELD_SCM_AUTHOR, "polop")
+      .put(FIELD_SOURCE, "package org.sonar.server.source;")
+      .put(FIELD_DUPLICATIONS, duplications)
       .put(BaseNormalizer.UPDATED_AT_FIELD, new Date())
       .build());
     SourceLineResultSetIterator.SourceFile file = new SourceLineResultSetIterator.SourceFile("efgh", System.currentTimeMillis());
@@ -94,26 +99,74 @@ public class SourceLineIndexerTest {
 
     assertThat(countDocuments()).isEqualTo(2L);
 
-    SearchResponse fileSearch = es.client().prepareSearch(SourceLineIndexDefinition.INDEX)
-      .setTypes(SourceLineIndexDefinition.TYPE)
-      .setQuery(QueryBuilders.termQuery(SourceLineIndexDefinition.FIELD_FILE_UUID, "efgh"))
+    SearchResponse fileSearch = prepareSearch()
+      .setQuery(QueryBuilders.termQuery(FIELD_FILE_UUID, "efgh"))
       .get();
     assertThat(fileSearch.getHits().getTotalHits()).isEqualTo(1L);
     Map<String, Object> fields = fileSearch.getHits().getHits()[0].sourceAsMap();
     assertThat(fields).hasSize(9);
     assertThat(fields).includes(
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_PROJECT_UUID, "abcd"),
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_FILE_UUID, "efgh"),
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_LINE, 1),
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_SCM_REVISION, "cafebabe"),
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_SCM_DATE, "2014-01-01T11:34:56.000Z"),
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_SCM_AUTHOR, "polop"),
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_SOURCE, "package org.sonar.server.source;"),
-      MapAssert.entry(SourceLineIndexDefinition.FIELD_DUPLICATIONS, duplications)
+      MapAssert.entry(FIELD_PROJECT_UUID, "abcd"),
+      MapAssert.entry(FIELD_FILE_UUID, "efgh"),
+      MapAssert.entry(FIELD_LINE, 1),
+      MapAssert.entry(FIELD_SCM_REVISION, "cafebabe"),
+      MapAssert.entry(FIELD_SCM_DATE, "2014-01-01T11:34:56.000Z"),
+      MapAssert.entry(FIELD_SCM_AUTHOR, "polop"),
+      MapAssert.entry(FIELD_SOURCE, "package org.sonar.server.source;"),
+      MapAssert.entry(FIELD_DUPLICATIONS, duplications)
       );
   }
 
+  @Test
+  public void delete_file_uuids() throws Exception {
+    addSource("line2.json");
+    addSource("line3.json");
+    addSource("line2_other_file.json");
+
+    indexer.deleteByFiles(Lists.newArrayList("efgh"));
+
+    List<SearchHit> hits = getDocuments();
+    Map<String, Object> document = hits.get(0).getSource();
+    assertThat(hits).hasSize(1);
+    assertThat(document.get(FIELD_LINE)).isEqualTo(2);
+    assertThat(document.get(FIELD_FILE_UUID)).isEqualTo("fdsq");
+  }
+
+  @Test
+  public void delete_by_project_uuid() throws Exception {
+    addSource("line2.json");
+    addSource("line3.json");
+    addSource("line2_other_file.json");
+    addSource("line3_other_project.json");
+
+    indexer.deleteByProject("abcd");
+
+    List<SearchHit> hits = getDocuments();
+    Map<String, Object> document = hits.get(0).getSource();
+    assertThat(hits).hasSize(1);
+    assertThat(document.get(FIELD_PROJECT_UUID)).isEqualTo("plmn");
+  }
+
+  private void addSource(String fileName) throws Exception {
+    prepareIndex()
+      .setSource(IOUtils.toString(new FileInputStream(TestUtils.getResource(this.getClass(), fileName))))
+      .get();
+  }
+
+  private SearchRequestBuilder prepareSearch() {
+    return es.client().prepareSearch(INDEX)
+      .setTypes(TYPE);
+  }
+
+  private IndexRequestBuilder prepareIndex() {
+    return es.client().prepareIndex(INDEX, TYPE);
+  }
+
+  private List<SearchHit> getDocuments() {
+    return es.getDocuments(INDEX, TYPE);
+  }
+
   private long countDocuments() {
-    return es.countDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE);
+    return es.countDocuments(INDEX, TYPE);
   }
 }
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexerTest/line3.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexerTest/line3.json
new file mode 100644 (file)
index 0000000..a6899ae
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "projectUuid": "abcd",
+  "fileUuid": "efgh",
+  "line": 3,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00",
+  "utLineHits": 0,
+  "utConditions": 0,
+  "utCoveredConditions": 0,
+  "itLineHits": 0,
+  "itConditions": 0,
+  "itCoveredConditions": 0,
+  "overallLineHits": 0,
+  "overallConditions": 0,
+  "overallCoveredConditions": 0
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexerTest/line3_other_project.json b/server/sonar-server/src/test/resources/org/sonar/server/source/index/SourceLineIndexerTest/line3_other_project.json
new file mode 100644 (file)
index 0000000..107ccb1
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "projectUuid": "plmn",
+  "fileUuid": "efgh",
+  "line": 3,
+  "scmAuthor": "polop",
+  "scmDate": "2014-01-01T12:34:56.7+01:00",
+  "scmRevision": "cafebabe",
+  "source": "// Empty",
+  "updatedAt": "2014-01-01T23:45:01.8+01:00",
+  "utLineHits": 0,
+  "utConditions": 0,
+  "utCoveredConditions": 0,
+  "itLineHits": 0,
+  "itConditions": 0,
+  "itCoveredConditions": 0,
+  "overallLineHits": 0,
+  "overallConditions": 0,
+  "overallCoveredConditions": 0
+}
\ No newline at end of file
index 96b391a06ea0fa5e50af9d54aa84ed362f015350..931a746b6203e9108dfc434c3fc80f4681ab86d3 100644 (file)
@@ -32,6 +32,8 @@ import org.sonar.core.purge.PurgeConfiguration;
 import org.sonar.core.purge.PurgeDao;
 import org.sonar.core.purge.PurgeProfiler;
 
+import java.util.List;
+
 public class ProjectPurgeTask implements ServerComponent {
   private static final Logger LOG = LoggerFactory.getLogger(ProjectPurgeTask.class);
   private final PurgeProfiler profiler;
@@ -75,4 +77,8 @@ public class ProjectPurgeTask implements ServerComponent {
       LOG.error("Fail to purge data [id=" + configuration.rootProjectId() + "]", e);
     }
   }
+
+  public List<String> findUuidsToDisable(DbSession session, Long projectId) {
+    return purgeDao.selectPurgeableFiles(session, projectId);
+  }
 }
index c43f3df6d93632a00a1ddf7912dbea372061662b..9eb298082ab0cec52782778ce1272314634ec159 100644 (file)
@@ -45,7 +45,7 @@ public class PurgeDao {
   private final MyBatis mybatis;
   private final ResourceDao resourceDao;
   private final System2 system2;
-  private PurgeProfiler profiler;
+  private final PurgeProfiler profiler;
 
   public PurgeDao(MyBatis mybatis, ResourceDao resourceDao, PurgeProfiler profiler, System2 system2) {
     this.mybatis = mybatis;
@@ -222,4 +222,8 @@ public class PurgeDao {
     return projects;
   }
 
+  public List<String> selectPurgeableFiles(DbSession dbSession, Long projectId) {
+    PurgeMapper mapper = dbSession.getMapper(PurgeMapper.class);
+    return mapper.selectPurgeableFileUuids(projectId);
+  }
 }
index f498b1224961db63e6da246e9d584e30ab26451f..a31a2abd2b9d9bc3abe38e7c21e42a59858ce2c5 100644 (file)
@@ -107,4 +107,6 @@ public interface PurgeMapper {
   void deleteFileSourcesByProjectUuid(String rootProjectUuid);
 
   void deleteFileSourcesByUuid(String fileUuid);
+
+  List<String> selectPurgeableFileUuids(Long projectId);
 }
index c017309048503b6015756ab85b2a08841e49fbb1..6134cde6f24223f8d14cfaecd233913e56e85238 100644 (file)
     and not exists(select s.project_id from snapshots s where s.islast=${_true} and s.project_id=p.id)
   </select>
 
+  <select id="selectPurgeableFileUuids" resultType="string" parameterType="long">
+    select p.uuid from projects p
+    where (p.id=#{id} or p.root_id=#{id}) and p.enabled=${_true} and p.scope='FIL'
+    and not exists(select s.project_id from snapshots s where s.islast=${_true} and s.project_id=p.id)
+  </select>
+
   <select id="selectMetricIdsWithoutHistoricalData" resultType="long">
     select id from metrics where delete_historical_data=${_true}
   </select>
index e0bc253992d17bdf0c8a1ab2e50aec22b32b3acd..8487e4076aaefefce7e51dea4e779dc5bbb022fc 100644 (file)
  */
 package org.sonar.core.purge;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.resources.Scopes;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
 import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.resource.ResourceDao;
 
 import java.util.List;
@@ -35,9 +38,9 @@ import static org.mockito.Mockito.when;
 
 public class PurgeDaoTest extends AbstractDaoTestCase {
 
-  System2 system2;
-
-  PurgeDao dao;
+  private PurgeDao sut;
+  private System2 system2;
+  private DbSession dbSession;
 
   private static PurgeableSnapshotDto getById(List<PurgeableSnapshotDto> snapshots, long id) {
     for (PurgeableSnapshotDto snapshot : snapshots) {
@@ -49,59 +52,65 @@ public class PurgeDaoTest extends AbstractDaoTestCase {
   }
 
   @Before
-  public void createDao() {
+  public void before() {
     system2 = mock(System2.class);
     when(system2.now()).thenReturn(DateUtils.parseDate("2014-04-09").getTime());
+    dbSession = getMyBatis().openSession(false);
+
+    sut = new PurgeDao(getMyBatis(), new ResourceDao(getMyBatis(), system2), new PurgeProfiler(), system2);
+  }
 
-    dao = new PurgeDao(getMyBatis(), new ResourceDao(getMyBatis(), system2), new PurgeProfiler(), system2);
+  @After
+  public void after() {
+    MyBatis.closeQuietly(dbSession);
   }
 
   @Test
   public void shouldDeleteAbortedBuilds() {
     setupData("shouldDeleteAbortedBuilds");
-    dao.purge(new PurgeConfiguration(1L, new String[0], 30));
+    sut.purge(new PurgeConfiguration(1L, new String[0], 30));
     checkTables("shouldDeleteAbortedBuilds", "snapshots");
   }
 
   @Test
   public void should_purge_project() {
     setupData("shouldPurgeProject");
-    dao.purge(new PurgeConfiguration(1L, new String[0], 30));
+    sut.purge(new PurgeConfiguration(1L, new String[0], 30));
     checkTables("shouldPurgeProject", "projects", "snapshots");
   }
 
   @Test
   public void delete_file_sources_of_disabled_resources() {
     setupData("delete_file_sources_of_disabled_resources");
-    dao.purge(new PurgeConfiguration(1L, new String[0], 30, system2));
+    sut.purge(new PurgeConfiguration(1L, new String[0], 30, system2));
     checkTables("delete_file_sources_of_disabled_resources", "file_sources");
   }
 
   @Test
   public void shouldDeleteHistoricalDataOfDirectoriesAndFiles() {
     setupData("shouldDeleteHistoricalDataOfDirectoriesAndFiles");
-    dao.purge(new PurgeConfiguration(1L, new String[] {Scopes.DIRECTORY, Scopes.FILE}, 30));
+    sut.purge(new PurgeConfiguration(1L, new String[] {Scopes.DIRECTORY, Scopes.FILE}, 30));
     checkTables("shouldDeleteHistoricalDataOfDirectoriesAndFiles", "projects", "snapshots");
   }
 
   @Test
   public void disable_resources_without_last_snapshot() {
     setupData("disable_resources_without_last_snapshot");
-    dao.purge(new PurgeConfiguration(1L, new String[0], 30, system2));
+    sut.purge(new PurgeConfiguration(1L, new String[0], 30, system2));
     checkTables("disable_resources_without_last_snapshot", "projects", "snapshots", "issues");
   }
 
   @Test
   public void shouldDeleteSnapshots() {
     setupData("shouldDeleteSnapshots");
-    dao.deleteSnapshots(PurgeSnapshotQuery.create().setIslast(false).setResourceId(1L));
+    sut.deleteSnapshots(PurgeSnapshotQuery.create().setIslast(false).setResourceId(1L));
     checkTables("shouldDeleteSnapshots", "snapshots");
   }
 
   @Test
   public void shouldSelectPurgeableSnapshots() {
     setupData("shouldSelectPurgeableSnapshots");
-    List<PurgeableSnapshotDto> snapshots = dao.selectPurgeableSnapshots(1L);
+    List<PurgeableSnapshotDto> snapshots = sut.selectPurgeableSnapshots(1L);
 
     assertThat(snapshots).hasSize(3);
     assertThat(getById(snapshots, 1L).isLast()).isTrue();
@@ -115,21 +124,30 @@ public class PurgeDaoTest extends AbstractDaoTestCase {
   @Test
   public void should_delete_project_and_associated_data() {
     setupData("shouldDeleteProject");
-    dao.deleteResourceTree(new IdUuidPair(1L, "A"));
+    sut.deleteResourceTree(new IdUuidPair(1L, "A"));
     assertEmptyTables("projects", "snapshots", "action_plans", "issues", "issue_changes", "file_sources");
   }
 
   @Test
   public void should_delete_old_closed_issues() {
     setupData("should_delete_old_closed_issues");
-    dao.purge(new PurgeConfiguration(1L, new String[0], 30));
+    sut.purge(new PurgeConfiguration(1L, new String[0], 30));
     checkTables("should_delete_old_closed_issues", "issues", "issue_changes");
   }
 
   @Test
   public void should_delete_all_closed_issues() {
     setupData("should_delete_all_closed_issues");
-    dao.purge(new PurgeConfiguration(1L, new String[0], 0));
+    sut.purge(new PurgeConfiguration(1L, new String[0], 0));
     checkTables("should_delete_all_closed_issues", "issues", "issue_changes");
   }
+
+  @Test
+  public void select_purgeable_file_uuids_and_only_them() {
+    setupData("select_purgeable_file_uuids");
+
+    List<String> uuids = sut.selectPurgeableFiles(dbSession, 1L);
+
+    assertThat(uuids).containsOnly("GHIJ");
+  }
 }
diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/select_purgeable_file_uuids.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/select_purgeable_file_uuids.xml
new file mode 100644 (file)
index 0000000..2925fef
--- /dev/null
@@ -0,0 +1,86 @@
+<dataset>
+
+  <!-- the project -->
+  <projects id="1" enabled="[true]" root_id="[null]" uuid="ABCD" project_uuid="ABCD" module_uuid="[null]"
+            module_uuid_path="[null]" created_at="[null]"
+            long_name="[null]" scope="PRJ" qualifier="TRK" kee="project" name="project"
+            description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+            deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+  <!-- the directory -->
+  <projects id="2" enabled="[true]" root_id="1" uuid="EFGH" project_uuid="ABCD" module_uuid="ABCD"
+            module_uuid_path="[null]" created_at="[null]"
+            long_name="[null]" scope="DIR" qualifier="DIR" kee="project:my/dir" name="my/dir"
+            description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+            deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+  <!-- the files -->
+  <projects id="3" enabled="[true]" root_id="1" uuid="GHIJ" project_uuid="ABCD" module_uuid="ABCD"
+            module_uuid_path="ABCD" created_at="[null]"
+            long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java" name="my/dir/File.java"
+            description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+            deprecated_kee="[null]" authorization_updated_at="[null]"/>
+  <projects id="4" enabled="[true]" root_id="1" uuid="KLMN" project_uuid="ABCD" module_uuid="ABCD"
+            module_uuid_path="ABCD" created_at="[null]"
+            long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java"
+            name="my/dir/File.java"
+            description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+            deprecated_kee="[null]" authorization_updated_at="[null]"/>
+  <!-- the file has already been disabled. It should not be selected -->
+  <projects id="5" enabled="[false]" root_id="1" uuid="OPQR" project_uuid="ABCD" module_uuid="ABCD"
+            module_uuid_path="ABCD" created_at="[null]"
+            long_name="[null]" scope="FIL" qualifier="FIL" kee="project:my/dir/File.java"
+            name="my/dir/File.java"
+            description="[null]" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]"
+            deprecated_kee="[null]" authorization_updated_at="[null]"/>
+
+  <snapshots id="1"
+             project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+             status="P" islast="[true]" purge_status="[null]"
+             period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+             period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+             period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+             period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+             period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+             depth="[null]" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00"
+             build_date="2008-12-02 13:58:00.00" version="[null]" path="[null]"/>
+
+  <snapshots id="2"
+             project_id="2" parent_snapshot_id="1" root_project_id="1" root_snapshot_id="1"
+             status="P" islast="[true]" purge_status="[null]"
+             period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+             period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+             period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+             period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+             period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+             depth="[null]" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00"
+             build_date="2008-12-02 13:58:00.00" version="[null]" path="[null]"/>
+
+  <!-- isLast is false -->
+  <snapshots id="3"
+             project_id="3" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+             status="P" islast="[false]" purge_status="[null]"
+             period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+             period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+             period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+             period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+             period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+             depth="[null]" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00"
+             build_date="2008-12-02 13:58:00.00" version="[null]" path="[null]"/>
+
+  <snapshots id="4"
+             project_id="4" parent_snapshot_id="2" root_project_id="1" root_snapshot_id="1"
+             status="P" islast="[true]" purge_status="[null]"
+             period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+             period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+             period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+             period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+             period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+             depth="[null]" scope="PRJ" qualifier="TRK" created_at="2008-12-02 13:58:00.00"
+             build_date="2008-12-02 13:58:00.00" version="[null]" path="[null]"/>
+
+  <file_sources id="1" project_uuid="ABCD" file_uuid="GHIJ" data="[null]" line_hashes="[null]" data_hash="321654987"
+                created_at="123456789" updated_at="123456789"/>
+  <file_sources id="2" project_uuid="ABCD" file_uuid="KLMN" data="[null]" line_hashes="[null]" data_hash="321654988"
+                created_at="123456789" updated_at="123456789"/>
+</dataset>