]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8238 when updating a project, clear ALL components from index
authorDaniel Schwarz <bartfastiel@users.noreply.github.com>
Mon, 23 Jan 2017 15:08:49 +0000 (16:08 +0100)
committerGitHub <noreply@github.com>
Mon, 23 Jan 2017 15:08:49 +0000 (16:08 +0100)
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java
sonar-db/src/main/java/org/sonar/db/component/ComponentDao.java
sonar-db/src/main/java/org/sonar/db/component/ComponentMapper.java
sonar-db/src/main/resources/org/sonar/db/component/ComponentMapper.xml
sonar-db/src/test/java/org/sonar/db/component/ComponentDaoTest.java
sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/selectForIndexing.xml [new file with mode: 0644]

index 9f628f9ac75ffb4699cf791709f31563cd99d865..31a5e4c0e7d65db3b783e2813edb5d672e29f9f9 100644 (file)
@@ -28,6 +28,7 @@ import java.util.concurrent.Future;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
 import org.elasticsearch.action.index.IndexRequest;
 import org.sonar.api.Startable;
 import org.sonar.db.DbClient;
@@ -36,6 +37,9 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.EsClient;
 
+import static java.util.Objects.requireNonNull;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_COMPONENTS;
 import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_AUTHORIZATION;
 import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
@@ -55,47 +59,53 @@ public class ComponentIndexer implements Startable {
   /**
    * Copy all components of all projects to the elastic search index.
    * <p>
-   * <b>Warning</b>: This should only be called on an empty index. It does not delete anything.
+   * <b>Warning</b>: This should only be called on an empty index and it should only be called during startup.
    */
   public void index() {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      BulkIndexer bulk = new BulkIndexer(esClient, INDEX_COMPONENTS);
-      bulk.setLarge(true);
-      bulk.start();
-      dbClient.componentDao()
-        .selectAll(dbSession, context -> {
-          ComponentDto dto = (ComponentDto) context.getResultObject();
-          bulk.add(newIndexRequest(toDocument(dto)));
-        });
-      bulk.stop();
-    }
+    doIndexByProjectUuid(null);
   }
 
   /**
    * Update the index for one specific project. The current data from the database is used.
    */
   public void indexByProjectUuid(String projectUuid) {
+    requireNonNull(projectUuid);
+    deleteComponentsByProjectUuid(projectUuid);
+    doIndexByProjectUuid(projectUuid);
+  }
+
+  /**
+   * @param projectUuid the uuid of the project to analyze, or <code>null</code> if all content should be indexed.<br/>
+   * <b>Warning:</b> only use <code>null</code> during startup.
+   */
+  private void doIndexByProjectUuid(@Nullable String projectUuid) {
+    BulkIndexer bulk = new BulkIndexer(esClient, INDEX_COMPONENTS);
+
+    // setLarge must be enabled only during server startup because it disables replicas
+    bulk.setLarge(projectUuid == null);
+
+    bulk.start();
     try (DbSession dbSession = dbClient.openSession(false)) {
-      deleteComponentsByProjectUuid(projectUuid);
-      index(
-        dbClient
-          .componentDao()
-          .selectByProjectUuid(projectUuid, dbSession)
-          .toArray(new ComponentDto[0]));
+      dbClient.componentDao()
+        .selectForIndexing(dbSession, projectUuid, context -> {
+          ComponentDto dto = (ComponentDto) context.getResultObject();
+          bulk.add(newIndexRequest(toDocument(dto)));
+        });
     }
+    bulk.stop();
   }
 
-  public void deleteByProjectUuid(String uuid) {
-    deleteComponentsByProjectUuid(uuid);
-    deleteAuthorizationByProjectUuid(uuid);
+  public void deleteByProjectUuid(String projectUuid) {
+    requireNonNull(projectUuid);
+    deleteComponentsByProjectUuid(projectUuid);
+    deleteAuthorizationByProjectUuid(projectUuid);
   }
 
   private void deleteComponentsByProjectUuid(String projectUuid) {
-    esClient
-      .prepareDelete(INDEX_COMPONENTS, TYPE_COMPONENT, projectUuid)
-      .setRouting(projectUuid)
-      .setRefresh(true)
-      .get();
+    BulkIndexer.delete(esClient, INDEX_COMPONENTS, esClient.prepareSearch(INDEX_COMPONENTS)
+      .setQuery(boolQuery()
+        .filter(
+          termQuery(ComponentIndexDefinition.FIELD_PROJECT_UUID, projectUuid))));
   }
 
   private void deleteAuthorizationByProjectUuid(String projectUuid) {
index 23bea430a5b43cece4c78e91f51a7a6b9b335a06..789105833f887a749f12b88732051aae803bf23d 100644 (file)
@@ -101,7 +101,7 @@ public class ComponentIndexerTest {
   }
 
   @Test
-  public void reindex_project() {
+  public void index_and_update_and_reindex_project() {
 
     // insert
     ComponentDto component = ComponentTesting.newProjectDto(organization, "UUID-1").setName("OldName");
@@ -121,16 +121,36 @@ public class ComponentIndexerTest {
     assertMatches("NewName", 1);
   }
 
+  @Test
+  public void index_and_update_and_reindex_project_with_files() {
+
+    // insert
+    ComponentDto project = dbTester.components().insertProject();
+    ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project).setName("OldFile"));
+
+    // verify insert
+    index(project);
+    assertMatches("OldFile", 1);
+
+    // modify
+    file.setName("NewFile");
+    update(file);
+
+    // verify modification
+    index(project);
+    assertMatches("OldFile", 0);
+    assertMatches("NewFile", 1);
+  }
+
   private void insert(ComponentDto component) {
-    dbClient.componentDao().insert(dbSession, component);
-    dbSession.commit();
+    dbTester.components().insertComponent(component);
   }
 
   private void update(ComponentDto component) {
     ComponentUpdateDto updateComponent = ComponentUpdateDto.copyFrom(component);
     updateComponent.setBChanged(true);
     dbClient.componentDao().update(dbSession, updateComponent);
-    dbClient.componentDao().applyBChangesForRootComponentUuid(dbSession, "UUID-1");
+    dbClient.componentDao().applyBChangesForRootComponentUuid(dbSession, component.getRootUuid());
     dbSession.commit();
   }
 
@@ -158,7 +178,7 @@ public class ComponentIndexerTest {
         .setQuery(termQuery(FIELD_NAME, nameQuery))
         .get()
         .getHits()
-        .getHits().length).isEqualTo(numberOfMatches);
+        .getTotalHits()).isEqualTo(numberOfMatches);
   }
 
   private ComponentIndexer createIndexer() {
index 8ec27d1c2a6c05643b9920d3a8dc4e8a7c5bd48e..0bc027afb7a7d26cfd79f13e24dd89474269f34a 100644 (file)
@@ -28,6 +28,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import javax.annotation.Nullable;
 import org.apache.ibatis.session.ResultHandler;
@@ -230,10 +231,14 @@ public class ComponentDao implements Dao {
   }
 
   /**
-   * Selects all enabled components. The result is not returned (since it is usually too big), but handed over to the <code>handler</code>
+   * Selects all components that are relevant for indexing. The result is not returned (since it is usually too big), but handed over to the <code>handler</code>
+   * @param session the database session
+   * @param projectUuid the project uuid, which is selected with all of its children
+   * @param handler the action to be applied to every result
    */
-  public void selectAll(DbSession session, ResultHandler handler) {
-    mapper(session).selectAll(handler);
+  public void selectForIndexing(DbSession session, @Nullable String projectUuid, ResultHandler handler) {
+    Objects.requireNonNull(handler);
+    mapper(session).selectForIndexing(projectUuid, handler);
   }
 
   /**
index 7b2e26eab243fadf0b0ee2d011f45589c2a34ecb..a32a4177b589340f3053e74e971e5d813882103a 100644 (file)
@@ -116,7 +116,7 @@ public interface ComponentMapper {
 
   long countGhostProjects(Map<String, Object> parameters);
 
-  void selectAll(ResultHandler handler);
+  void selectForIndexing(@Param("projectUuid") @Nullable String projectUuid, ResultHandler handler);
 
   void insert(ComponentDto componentDto);
 
index a9d12f018718cb90bdbc712c1d59d4ad0f8335aa..92c23601b28f77945f328a8aa2a49e6c320e13b8 100644 (file)
     </if>
   </sql>
 
-  <select id="selectAll" parameterType="map" resultType="Component">
-    select distinct
-    <include refid="componentColumns"/>
+  <select id="selectForIndexing" parameterType="map" resultType="Component">
+    select
+      <include refid="componentColumns"/>
     from projects p
-    where p.enabled=${_true}
+    where
+      p.enabled=${_true}
+      and p.copy_component_uuid is null
+      <if test="projectUuid != null">
+        and p.project_uuid=#{projectUuid}
+      </if>
   </select>
 
   <insert id="insert" parameterType="Component" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
index 039891ac0a16142ff9361214c16eecd83602cb82..8d2b2d83c6620bdeae49457207fcec0b53fc0340 100644 (file)
 package org.sonar.db.component;
 
 import com.google.common.base.Optional;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.ibatis.session.ResultContext;
+import org.apache.ibatis.session.ResultHandler;
+import org.assertj.core.api.ListAssert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -563,6 +568,36 @@ public class ComponentDaoTest {
     assertThat(components).extracting("id").containsOnly(1L, 2L, 3L, 4L);
   }
 
+  @Test
+  public void selectForIndexing_all() {
+    assertSelectForIndexing(null)
+      .doesNotContain("DIS7")
+      .doesNotContain("COPY8")
+      .containsOnly("U1", "U2", "U3", "U4", "U5", "U6");
+  }
+
+  @Test
+  public void selectForIndexing_project() {
+    assertSelectForIndexing("U1")
+      .doesNotContain("DIS7")
+      .doesNotContain("COPY8")
+      .containsOnly("U1", "U2", "U3", "U4");
+  }
+
+  private ListAssert<String> assertSelectForIndexing(@Nullable String projectUuid) {
+    db.prepareDbUnit(getClass(), "selectForIndexing.xml");
+
+    List<ComponentDto> components = new ArrayList<>();
+    underTest.selectForIndexing(dbSession, projectUuid, new ResultHandler() {
+
+      @Override
+      public void handleResult(ResultContext context) {
+        components.add((ComponentDto) context.getResultObject());
+      }
+    });
+    return assertThat(components).extracting(ComponentDto::uuid);
+  }
+
   @Test
   public void insert() {
     db.prepareDbUnit(getClass(), "empty.xml");
diff --git a/sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/selectForIndexing.xml b/sonar-db/src/test/resources/org/sonar/db/component/ComponentDaoTest/selectForIndexing.xml
new file mode 100644 (file)
index 0000000..7c4f24e
--- /dev/null
@@ -0,0 +1,185 @@
+<dataset>
+
+  <organizations uuid="org1"
+                 kee="org1_key"
+                 name="org1_name"
+                 created_at="1000"
+                 updated_at="1000"/>
+
+  <!-- root project -->
+  <projects organization_uuid="org1"
+            id="1"
+            scope="PRJ"
+            qualifier="TRK"
+            kee="org.struts:struts"
+            deprecated_kee="org.struts:struts"
+            name="Struts"
+            uuid="U1"
+            uuid_path="uuid_path_of_U1"
+            root_uuid="U1"
+            project_uuid="U1"
+            module_uuid="module_uuid_of_U1"
+            module_uuid_path="module_uuid_path_of_U1"
+            description="the description"
+            long_name="Apache Struts"
+            enabled="[true]"
+            language="java"
+            copy_component_uuid="[null]"
+            developer_uuid="[null]"
+            path="path_of_U1"
+            authorization_updated_at="123456789"/>
+
+  <!-- module -->
+  <projects organization_uuid="org1"
+            id="2"
+            kee="org.struts:struts-core"
+            name="Struts Core"
+            uuid="U2"
+            uuid_path="uuid_path_of_U2"
+            project_uuid="U1"
+            root_uuid="U1"
+            module_uuid="[null]"
+            module_uuid_path="module_uuid_path_of_U2"
+            scope="PRJ"
+            qualifier="BRC"
+            long_name="Struts Core"
+            description="[null]"
+            enabled="[true]"
+            language="[null]"
+            copy_component_uuid="[null]"
+            developer_uuid="[null]"
+            authorization_updated_at="[null]"/>
+
+  <!-- directory -->
+  <projects organization_uuid="org1"
+            long_name="org.struts"
+            id="3"
+            scope="DIR"
+            qualifier="DIR"
+            kee="org.struts:struts-core:src/org/struts"
+            uuid="U3"
+            uuid_path="uuid_path_of_U3"
+            project_uuid="U1"
+            root_uuid="U1"
+            module_uuid="module_uuid_of_U3"
+            module_uuid_path="module_uuid_path_of_U3"
+            name="src/org/struts"
+            description="[null]"
+            enabled="[true]"
+            language="[null]"
+            copy_component_uuid="[null]"
+            developer_uuid="[null]"
+            path="src/org/struts"
+            authorization_updated_at="[null]"/>
+
+  <!-- file -->
+  <projects organization_uuid="org1"
+            long_name="org.struts.RequestContext"
+            id="4"
+            scope="FIL"
+            qualifier="FIL"
+            kee="org.struts:struts-core:src/org/struts/RequestContext.java"
+            uuid="U4"
+            uuid_path="uuid_path_of_U4"
+            project_uuid="U1"
+            root_uuid="U1"
+            module_uuid="module_uuid_of_U4"
+            module_uuid_path="module_uuid_path_of_U4"
+            name="RequestContext.java"
+            description="[null]"
+            enabled="[true]"
+            language="java"
+            copy_component_uuid="[null]"
+            developer_uuid="[null]"
+            path="path_of_U4"
+            authorization_updated_at="[null]"/>
+            
+  <!-- root project -->
+  <projects organization_uuid="org1"
+            id="5"
+            scope="PRJ"
+            qualifier="TRK"
+            kee="org.paper:paper"
+            deprecated_kee="org.paper:paper"
+            name="Paper"
+            uuid="U5"
+            uuid_path="uuid_path_of_U5"
+            root_uuid="U5"
+            project_uuid="U5"
+            module_uuid="module_uuid_of_U5"
+            module_uuid_path="module_uuid_path_of_U5"
+            description="the description"
+            long_name="Some Paper"
+            enabled="[true]"
+            language="java"
+            copy_component_uuid="[null]"
+            developer_uuid="[null]"
+            path="path_of_U5"
+            authorization_updated_at="123456789"/>
+
+  <!-- module -->
+  <projects organization_uuid="org1"
+            id="6"
+            kee="org.paper:paper-core"
+            name="Paper Core"
+            uuid="U6"
+            uuid_path="uuid_path_of_U6"
+            project_uuid="U5"
+            root_uuid="U5"
+            module_uuid="[null]"
+            module_uuid_path="module_uuid_path_of_U6"
+            scope="PRJ"
+            qualifier="BRC"
+            long_name="Paper Core"
+            description="[null]"
+            enabled="[true]"
+            language="[null]"
+            copy_component_uuid="[null]"
+            developer_uuid="[null]"
+            authorization_updated_at="[null]"/>
+
+  <!-- Disabled projects -->
+  <projects organization_uuid="org1"
+            id="7"
+            scope="PRJ"
+            qualifier="TRK"
+            kee="org.disabled.project"
+            name="Disabled Project"
+            uuid="DIS7"
+            uuid_path="uuid_path_of_DIS7"
+            project_uuid="project_uuid_of_DIS7"
+            root_uuid="root_uuid_of_DIS7"
+            module_uuid="[null]"
+            module_uuid_path="module_uuid_path_of_DIS7"
+            description="the description"
+            long_name="Disabled project"
+            enabled="[false]"
+            language="[null]"
+            copy_component_uuid="[null]"
+            developer_uuid="[null]"
+            path="[null]"
+            authorization_updated_at="[null]"/>
+            
+  <!-- copy component projects -->
+  <projects organization_uuid="org1"
+            id="8"
+            scope="PRJ"
+            qualifier="TRK"
+            kee="org.copy.project"
+            name="Copy Project"
+            uuid="COPY8"
+            uuid_path="uuid_path_of_COPY8"
+            project_uuid="project_uuid_of_COPY8"
+            root_uuid="root_uuid_of_COPY8"
+            module_uuid="[null]"
+            module_uuid_path="module_uuid_path_of_COPY8"
+            description="the description"
+            long_name="Copy project"
+            enabled="[true]"
+            language="[null]"
+            copy_component_uuid="U1"
+            developer_uuid="[null]"
+            path="[null]"
+            authorization_updated_at="[null]"/>
+
+</dataset>