]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17093 - Improve pull endpoints performance
authorBelen Pruvost <belen.pruvost@sonarsource.com>
Tue, 26 Jul 2022 09:54:03 +0000 (11:54 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 26 Jul 2022 20:03:42 +0000 (20:03 +0000)
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BasePullAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/PullAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/PullTaintAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullActionIssuesRetriever.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/pull/PullActionIssuesRetrieverTest.java

index c95ee71c47c17dea36666eb1c4c301795614cdfa..00a47fe64ee2639edd308f4f8e9eafeec03e1e57 100644 (file)
@@ -23,7 +23,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
-import javax.annotation.Nullable;
 import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
 import org.sonar.db.Pagination;
@@ -37,6 +36,7 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputs;
 
 public class IssueDao implements Dao {
   public static final int DEFAULT_PAGE_SIZE = 1000;
+  public static final int BIG_PAGE_SIZE = 1000000;
 
   public Optional<IssueDto> selectByKey(DbSession session, String key) {
     return Optional.ofNullable(mapper(session).selectByKey(key));
@@ -61,27 +61,26 @@ public class IssueDao implements Dao {
   }
 
   public Set<String> selectIssueKeysByComponentUuid(DbSession session, String componentUuid) {
-    return selectIssueKeysByComponentUuid(session, componentUuid, emptyList(), emptyList(), emptyList(),
-      null, false);
+    return mapper(session).selectIssueKeysByComponentUuid(componentUuid);
   }
 
   public Set<String> selectIssueKeysByComponentUuid(DbSession session, String componentUuid,
     List<String> includingRepositories, List<String> excludingRepositories,
-    List<String> languages, @Nullable Boolean resolvedOnly, boolean openIssuesOnly) {
-    return mapper(session).selectIssueKeysByComponentUuid(componentUuid, includingRepositories, excludingRepositories,
-      languages, resolvedOnly, openIssuesOnly);
+    List<String> languages, int page) {
+    return mapper(session).selectIssueKeysByComponentUuidWithFilters(componentUuid, includingRepositories, excludingRepositories,
+      languages, Pagination.forPage(page).andSize(BIG_PAGE_SIZE));
   }
 
-  public Set<String> selectIssueKeysByComponentUuidAndChangedSinceDate(DbSession session, String componentUuid, long changedSince) {
+  public Set<String> selectIssueKeysByComponentUuidAndChangedSinceDate(DbSession session, String componentUuid, long changedSince, int page) {
     return selectIssueKeysByComponentUuidAndChangedSinceDate(session, componentUuid, changedSince, emptyList(), emptyList(),
-      emptyList(), null);
+      emptyList(), page);
   }
 
   public Set<String> selectIssueKeysByComponentUuidAndChangedSinceDate(DbSession session, String componentUuid, long changedSince,
     List<String> includingRepositories, List<String> excludingRepositories,
-    List<String> languages, @Nullable Boolean resolvedOnly) {
+    List<String> languages, int page) {
     return mapper(session).selectIssueKeysByComponentUuidAndChangedSinceDate(componentUuid, changedSince,
-      includingRepositories, excludingRepositories, languages, resolvedOnly);
+      includingRepositories, excludingRepositories, languages, Pagination.forPage(page).andSize(BIG_PAGE_SIZE));
   }
 
   public List<IssueDto> selectByComponentUuidPaginated(DbSession session, String componentUuid, int page) {
@@ -145,9 +144,8 @@ public class IssueDao implements Dao {
     return session.getMapper(IssueMapper.class);
   }
 
-  public List<IssueDto> selectByBranch(DbSession dbSession, IssueQueryParams issueQueryParams, int page) {
-    Pagination pagination = Pagination.forPage(page).andSize(DEFAULT_PAGE_SIZE);
-    return mapper(dbSession).selectByBranch(issueQueryParams, pagination);
+  public List<IssueDto> selectByBranch(DbSession dbSession, Set<String> issueKeysSnapshot, IssueQueryParams issueQueryParams) {
+    return mapper(dbSession).selectByBranch(issueKeysSnapshot, issueQueryParams.getChangedSince());
   }
 
   public List<String> selectRecentlyClosedIssues(DbSession dbSession, IssueQueryParams issueQueryParams) {
index 3baf1db35497bed6b655528888e514cf115b062b..f040fc89480ca690a355b387e6e50cd5eb3d5409 100644 (file)
@@ -38,17 +38,18 @@ public interface IssueMapper {
 
   List<IssueDto> selectByKeys(List<String> keys);
 
-  Set<String> selectIssueKeysByComponentUuid(@Param("componentUuid") String componentUuid,
+  Set<String> selectIssueKeysByComponentUuid(@Param("componentUuid") String componentUuid);
+
+  Set<String> selectIssueKeysByComponentUuidWithFilters(@Param("componentUuid") String componentUuid,
     @Param("includingRepositories") List<String> includingRepositories,
     @Param("excludingRepositories") List<String> excludingRepositories,
-    @Param("languages") List<String> languages, @Param("resolvedOnly") @Nullable Boolean resolvedOnly,
-    @Param("openIssuesOnly") boolean openIssuesOnly);
+    @Param("languages") List<String> languages, @Param("pagination") Pagination pagination);
 
   Set<String> selectIssueKeysByComponentUuidAndChangedSinceDate(@Param("componentUuid") String componentUuid,
     @Param("changedSince") long changedSince,
     @Param("includingRepositories") List<String> includingRepositories,
     @Param("excludingRepositories") List<String> excludingRepositories,
-    @Param("languages") List<String> languages, @Param("resolvedOnly") @Nullable Boolean resolvedOnly);
+    @Param("languages") List<String> languages, @Param("pagination") Pagination pagination);
 
   List<IssueDto> selectByComponentUuidPaginated(@Param("componentUuid") String componentUuid,
     @Param("pagination") Pagination pagination);
@@ -79,9 +80,8 @@ public interface IssueMapper {
 
   Collection<IssueGroupDto> selectIssueGroupsByComponent(@Param("component") ComponentDto component, @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
 
+  List<IssueDto> selectByBranch(@Param("keys") Set<String> keys, @Nullable @Param("changedSince") Long changedSince);
 
-  List<IssueDto> selectByBranch(@Param("queryParams") IssueQueryParams issueQueryParams,
-    @Param("pagination") Pagination pagination);
 
   List<String> selectRecentlyClosedIssues(@Param("queryParams") IssueQueryParams issueQueryParams);
 }
index 0e1d3f71dd64280a219826021e86bcc7beea621e..ae2e340fa6414f0a3d6f2a4a8b1ccaa0452b3fdc 100644 (file)
     select
       i.kee
     from issues i
-   <if test="includingRepositories != null || excludingRepositories != null || languages != null">
-     inner join rules r on i.rule_uuid = r.uuid
-   </if>
+    where
+      i.project_uuid=#{componentUuid,jdbcType=VARCHAR}
+  </select>
+
+  <select id="selectIssueKeysByComponentUuidWithFilters" parameterType="string" resultType="string">
+    select
+      i.kee
+    from rules r
+    inner join issues i on i.rule_uuid = r.uuid
      where
       i.project_uuid=#{componentUuid,jdbcType=VARCHAR}
-    <choose>
-      <when test="openIssuesOnly == true">
-        AND i.issue_type != 4
-        AND i.status != 'CLOSED'
-      </when>
-      <when test="resolvedOnly == true">
-        AND i.issue_type != 4
-        AND i.status = 'RESOLVED'
-      </when>
-    </choose>
-    <if test="includingRepositories != null and includingRepositories.size() > 0">
+      AND i.status &lt;&gt; 'CLOSED'
+    <if test="includingRepositories.size() > 0">
         AND r.plugin_name IN
           <foreach item="ruleRepository" index="index" collection="includingRepositories" open="(" separator="," close=")">
             #{ruleRepository}
           </foreach>
     </if>
-    <if test="excludingRepositories != null and excludingRepositories.size() > 0">
+    <if test="excludingRepositories.size() > 0">
       AND r.plugin_name NOT IN
         <foreach item="ruleRepository" index="index" collection="excludingRepositories" open="(" separator="," close=")">
           #{ruleRepository}
         </foreach>
     </if>
-    <if test="languages != null and languages.size() > 0">
+    <if test="languages.size() > 0">
         AND r.language IN
           <foreach item="language" index="index" collection="languages" open="(" separator="," close=")">
               #{language}
           </foreach>
     </if>
+    order by i.kee
+    limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER}
+  </select>
+
+  <select id="selectIssueKeysByComponentUuidWithFilters" parameterType="map" resultType="string" databaseId="mssql">
+       select
+      i.kee
+    from
+    (select
+        row_number() over(order by i.kee ASC) as row_number,
+          i.kee
+        from rules r
+        inner join issues i on i.rule_uuid = r.uuid
+        where i.project_uuid=#{componentUuid,jdbcType=VARCHAR}
+        AND i.status &lt;&gt; 'CLOSED'
+        <if test="includingRepositories.size() > 0">
+            AND r.plugin_name IN
+              <foreach item="ruleRepository" index="index" collection="includingRepositories" open="(" separator="," close=")">
+                #{ruleRepository}
+              </foreach>
+        </if>
+        <if test="excludingRepositories.size() > 0">
+          AND r.plugin_name NOT IN
+            <foreach item="ruleRepository" index="index" collection="excludingRepositories" open="(" separator="," close=")">
+              #{ruleRepository}
+            </foreach>
+        </if>
+        <if test="languages.size() > 0">
+            AND r.language IN
+              <foreach item="language" index="index" collection="languages" open="(" separator="," close=")">
+                  #{language}
+              </foreach>
+        </if>
+      order by row_number asc
+      offset #{pagination.offset} rows
+      fetch next #{pagination.pageSize,jdbcType=INTEGER} rows only)  i
+   </select>
+
+  <select id="selectIssueKeysByComponentUuidWithFilters" parameterType="map" resultType="string" databaseId="oracle">
+    select
+      i.kee
+    from (
+             select rownum as rn, t.* from (
+               select
+                i.kee
+               from rules r
+          inner join issues i on i.rule_uuid = r.uuid
+               where i.project_uuid=#{componentUuid,jdbcType=VARCHAR}
+          AND i.status &lt;&gt; 'CLOSED'
+          <if test="includingRepositories.size() > 0">
+              AND r.plugin_name IN
+                <foreach item="ruleRepository" index="index" collection="includingRepositories" open="(" separator="," close=")">
+                  #{ruleRepository}
+                </foreach>
+          </if>
+          <if test="excludingRepositories.size() > 0">
+            AND r.plugin_name NOT IN
+              <foreach item="ruleRepository" index="index" collection="excludingRepositories" open="(" separator="," close=")">
+                #{ruleRepository}
+              </foreach>
+          </if>
+          <if test="languages.size() > 0">
+              AND r.language IN
+                <foreach item="language" index="index" collection="languages" open="(" separator="," close=")">
+                    #{language}
+                </foreach>
+          </if>
+          order by i.kee ASC
+             ) t ) i
+           where
+             i.rn between #{pagination.startRowNumber,jdbcType=INTEGER} and #{pagination.endRowNumber,jdbcType=INTEGER}
+           order by i.rn asc
   </select>
 
   <select id="selectIssueKeysByComponentUuidAndChangedSinceDate" parameterType="map" resultType="string">
     select
       i.kee
-    from issues i
-    <if test="includingRepositories != null || excludingRepositories != null || languages != null">
-       inner join rules r on i.rule_uuid = r.uuid
-    </if>
+    from rules r
+       inner join issues i on i.rule_uuid = r.uuid
     where
       i.project_uuid=#{componentUuid,jdbcType=VARCHAR}
       AND i.issue_update_date &gt;= #{changedSince,jdbcType=BIGINT}
-      AND i.status != 'CLOSED'
-      AND i.issue_type != 4
-    <if test="resolvedOnly == true">
-       AND i.status = 'RESOLVED'
-    </if>
-    <if test="includingRepositories != null and includingRepositories.size() > 0">
+      AND i.status &lt;&gt; 'CLOSED'
+    <if test="includingRepositories.size() > 0">
       AND r.plugin_name IN
         <foreach item="ruleRepository" index="index" collection="includingRepositories" open="(" separator="," close=")">
           #{ruleRepository}
         </foreach>
     </if>
-    <if test="excludingRepositories != null and excludingRepositories.size() > 0">
+    <if test="excludingRepositories.size() > 0">
       AND r.plugin_name NOT IN
         <foreach item="ruleRepository" index="index" collection="excludingRepositories" open="(" separator="," close=")">
           #{ruleRepository}
         </foreach>
     </if>
-    <if test="languages != null and languages.size() > 0">
+    <if test="languages.size() > 0">
       AND r.language IN
         <foreach item="language" index="index" collection="languages" open="(" separator="," close=")">
             #{language}
         </foreach>
    </if>
+    order by i.kee
+    limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER}
+  </select>
+
+  <select id="selectIssueKeysByComponentUuidAndChangedSinceDate" parameterType="map" resultType="string" databaseId="mssql">
+       select
+      i.kee
+    from
+    (select
+        row_number() over(order by i.kee ASC) as row_number,
+         i.kee
+        from rules r
+        inner join issues i on i.rule_uuid = r.uuid
+        where i.project_uuid=#{componentUuid,jdbcType=VARCHAR}
+        AND i.issue_update_date &gt;= #{changedSince,jdbcType=BIGINT}
+        AND i.status &lt;&gt; 'CLOSED'
+        <if test="includingRepositories.size() > 0">
+            AND r.plugin_name IN
+              <foreach item="ruleRepository" index="index" collection="includingRepositories" open="(" separator="," close=")">
+                #{ruleRepository}
+              </foreach>
+        </if>
+        <if test="excludingRepositories.size() > 0">
+          AND r.plugin_name NOT IN
+            <foreach item="ruleRepository" index="index" collection="excludingRepositories" open="(" separator="," close=")">
+              #{ruleRepository}
+            </foreach>
+        </if>
+        <if test="languages.size() > 0">
+            AND r.language IN
+              <foreach item="language" index="index" collection="languages" open="(" separator="," close=")">
+                  #{language}
+              </foreach>
+        </if>
+        order by row_number asc
+        offset #{pagination.offset} rows
+        fetch next #{pagination.pageSize,jdbcType=INTEGER} rows only)  i
+  </select>
+
+  <select id="selectIssueKeysByComponentUuidAndChangedSinceDate" parameterType="map" resultType="string" databaseId="oracle">
+    select
+      i.kee
+    from
+         (select rownum as rn, t.* from (
+               select
+                i.kee
+               from rules r
+          inner join issues i on i.rule_uuid = r.uuid
+               where i.project_uuid=#{componentUuid,jdbcType=VARCHAR}
+          AND i.issue_update_date &gt;= #{changedSince,jdbcType=BIGINT}
+          AND i.status &lt;&gt; 'CLOSED'
+          <if test="includingRepositories.size() > 0">
+              AND r.plugin_name IN
+                <foreach item="ruleRepository" index="index" collection="includingRepositories" open="(" separator="," close=")">
+                  #{ruleRepository}
+                </foreach>
+          </if>
+          <if test="excludingRepositories.size() > 0">
+            AND r.plugin_name NOT IN
+              <foreach item="ruleRepository" index="index" collection="excludingRepositories" open="(" separator="," close=")">
+                #{ruleRepository}
+              </foreach>
+          </if>
+          <if test="languages.size() > 0">
+              AND r.language IN
+                <foreach item="language" index="index" collection="languages" open="(" separator="," close=")">
+                    #{language}
+                </foreach>
+          </if>
+          order by i.kee ASC
+             ) t
+           ) i
+           where
+             i.rn between #{pagination.startRowNumber,jdbcType=INTEGER} and #{pagination.endRowNumber,jdbcType=INTEGER}
+           order by i.rn asc
   </select>
 
   <select id="selectByComponentUuidPaginated" parameterType="map" resultType="Issue">
     left join new_code_reference_issues n on i.kee = n.issue_key
   </select>
 
-
-  <sql id="selectByBranchColumnsFinal">
-    result.kee as kee,
-    result.ruleUuid as ruleUuid,
-    result.createdAt as createdAt,
-    result.status as status,
-    result.ruleType as ruleType,
-    result.ruleRepo as ruleRepo,
-    result.ruleKey as ruleKey,
-    result.message as message,
-    result.severity as severity,
-    result.manualSeverity as manualSeverity,
-    result.type as type,
-    result.locations as locations,
-    result.component_uuid,
-    c.path as filePath,
-    result.assigneeUuid
-  </sql>
-
-  <sql id="selectByBranchColumnsOuterQuery">
-    t.kee as kee,
-    t.ruleUuid as ruleUuid,
-    t.createdAt as createdAt,
-    t.status as status,
-    t.ruleType as ruleType,
-    t.ruleRepo as ruleRepo,
-    t.ruleKey as ruleKey,
-    t.message as message,
-    t.severity as severity,
-    t.manualSeverity as manualSeverity,
-    t.type as type,
-    t.locations as locations,
-    t.component_uuid,
-    t.assigneeUuid
-  </sql>
-
   <sql id="selectByBranchColumns">
     i.kee as kee,
     i.rule_uuid as ruleUuid,
         inner join rules r on r.uuid = i.rule_uuid
         inner join components p on p.uuid=i.component_uuid
     where
-      i.project_uuid = #{queryParams.branchUuid}
-      AND i.status &lt;&gt; 'CLOSED'
-      AND i.issue_type &lt;&gt; 4
-       <if test="queryParams.changedSince != null">
-        AND i.issue_update_date &gt;= #{queryParams.changedSince,jdbcType=BIGINT}
+      <if test="keys.size() > 0">
+        i.kee IN
+        <foreach collection="keys" open="(" close=")" item="key" separator=",">
+          #{key,jdbcType=VARCHAR}
+        </foreach>
+      </if>
+       <if test="changedSince != null">
+        AND i.issue_update_date &gt;= #{changedSince,jdbcType=BIGINT}
        </if>
-        <if test="queryParams.resolvedOnly == true">
-          AND i.status = 'RESOLVED'
-        </if>
-       <if test="queryParams.ruleRepositories.size() > 0">
-        AND r.plugin_name IN
-          <foreach item="ruleRepository" index="index" collection="queryParams.ruleRepositories" open="(" separator="," close=")">
-            #{ruleRepository}
-          </foreach>
-        </if>
-        <if test="queryParams.languages.size() > 0">
-          AND r.language IN
-            <foreach item="language" index="index" collection="queryParams.languages" open="(" separator="," close=")">
-                #{language}
-            </foreach>
-        </if>
-      order by i.kee ASC
-      limit #{pagination.pageSize,jdbcType=INTEGER} offset #{pagination.offset,jdbcType=INTEGER}
-  </select>
-
-  <select id="selectByBranch" parameterType="map" resultType="Issue" databaseId="mssql">
-    select
-      <include refid="selectByBranchColumnsFinal"/>
-    from
-    (select
-      <include refid="selectByBranchColumnsOuterQuery"/>
-    from (
-      select
-        row_number() over(order by i.kee ASC) as row_number,
-          <include refid="selectByBranchColumns"/>
-      from issues i
-          inner join rules r on r.uuid = i.rule_uuid
-      where
-        i.project_uuid = #{queryParams.branchUuid}
-        AND i.status &lt;&gt; 'CLOSED'
-        AND i.issue_type &lt;&gt; 4
-        <if test="queryParams.changedSince != null">
-            AND i.issue_update_date &gt;= #{queryParams.changedSince,jdbcType=BIGINT}
-        </if>
-        <if test="queryParams.resolvedOnly == true">
-          AND i.status = 'RESOLVED'
-        </if>
-        <if test="queryParams.ruleRepositories.size() > 0">
-          AND r.plugin_name IN
-            <foreach item="ruleRepository" index="index" collection="queryParams.ruleRepositories" open="(" separator="," close=")">
-              #{ruleRepository}
-            </foreach>
-        </if>
-        <if test="queryParams.languages.size() > 0">
-          AND r.language IN
-            <foreach item="language" index="index" collection="queryParams.languages" open="(" separator="," close=")">
-                #{language}
-            </foreach>
-        </if>
-      order by row_number asc
-      offset #{pagination.offset} rows
-      fetch next #{pagination.pageSize,jdbcType=INTEGER} rows only
-    ) as t) as result
-    inner join components c on c.uuid=result.component_uuid
-  </select>
-
-  <select id="selectByBranch" parameterType="map" resultType="Issue" databaseId="oracle">
-    select <include refid="selectByBranchColumnsFinal"/> from
-      (select <include refid="selectByBranchColumnsOuterQuery"/> from (
-        select rownum as rn, a.* from (
-          select
-            <include refid="selectByBranchColumns"/>
-          from issues i
-              inner join rules r on r.uuid = i.rule_uuid
-          where
-            i.project_uuid = #{queryParams.branchUuid}
-            AND i.status &lt;&gt; 'CLOSED'
-            AND i.issue_type &lt;&gt; 4
-            <if test="queryParams.changedSince != null">
-                AND i.issue_update_date &gt;= #{queryParams.changedSince,jdbcType=BIGINT}
-            </if>
-            <if test="queryParams.resolvedOnly == true">
-              AND i.status = 'RESOLVED'
-            </if>
-            <if test="queryParams.ruleRepositories.size() > 0">
-              AND r.plugin_name IN
-                <foreach item="ruleRepository" index="index" collection="queryParams.ruleRepositories" open="(" separator="," close=")">
-                  #{ruleRepository}
-                </foreach>
-            </if>
-            <if test="queryParams.languages.size() > 0">
-              AND r.language IN
-                <foreach item="language" index="index" collection="queryParams.languages" open="(" separator="," close=")">
-                    #{language}
-                </foreach>
-            </if>
-            order by i.kee ASC
-            ) a
-        ) t
-      where
-        t.rn between #{pagination.startRowNumber,jdbcType=INTEGER} and #{pagination.endRowNumber,jdbcType=INTEGER}
-      order by t.rn asc) result
-    inner join components c on c.uuid=result.component_uuid
   </select>
 
   <select id="selectRecentlyClosedIssues" resultType="string">
     select i.kee
-    from issues i
-        inner join rules r on r.uuid = i.rule_uuid
+    from rules r
+        inner join issues i on r.uuid = i.rule_uuid
     where
       i.project_uuid = #{queryParams.branchUuid}
       AND issue_update_date &gt;= #{queryParams.changedSince}
index fa29f8053a2c7692103da179eb52bb6b5a3b1a31..5537bbe44dd08b6bb26cead94efe2a8321b98c75 100644 (file)
@@ -167,25 +167,17 @@ public class IssueDaoTest {
 
     // Filter by including repositories
     Set<String> issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, List.of("xoo"),
-      emptyList(), emptyList(), null, false);
+      emptyList(), emptyList(), 1);
     // results are not ordered, so do not use "containsExactly"
-    assertThat(issues).containsOnly("I1", "I2", "I3");
+    assertThat(issues).containsOnly("I1", "I3");
 
     // Filter by excluding repositories
     issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, emptyList(), List.of("xoo"),
-      emptyList(), null, false);
+      emptyList(), 1);
     assertThat(issues).isEmpty();
 
     // Filter by language
-    issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, emptyList(), emptyList(), List.of("xoo"), null, false);
-    assertThat(issues).containsOnly("I1", "I2", "I3");
-
-    // Filter by resolved only
-    issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, emptyList(), emptyList(), emptyList(), true, false);
-    assertThat(issues).containsOnly("I1");
-
-    // Filter by non-closed issues only
-    issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, emptyList(), emptyList(), emptyList(), null, true);
+    issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, emptyList(), emptyList(), List.of("xoo"), 1);
     assertThat(issues).containsOnly("I1", "I3");
   }
 
@@ -198,10 +190,9 @@ public class IssueDaoTest {
     // Insert I3, I4, where t1 < t2
     IntStream.range(3, 5).forEach(i -> underTest.insert(db.getSession(), newIssueDto("I" + i).setUpdatedAt(t1)));
 
-    Set<String> issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2);
+    Set<String> issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2, 1);
 
-    // results are not ordered, so do not use "containsExactly"
-    assertThat(issues).containsOnly("I1");
+    assertThat(issues).contains("I1");
   }
 
   @Test
@@ -215,24 +206,19 @@ public class IssueDaoTest {
 
     // Filter by including repositories
     Set<String> issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2, List.of("xoo"),
-      emptyList(), emptyList(), null);
+      emptyList(), emptyList(), 1);
     // results are not ordered, so do not use "containsExactly"
-    assertThat(issues).containsOnly("I1");
+    assertThat(issues).contains("I1");
 
     // Filter by excluding repositories
     issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2,
-      emptyList(), List.of("xoo"), emptyList(), null);
+      emptyList(), List.of("xoo"), emptyList(), 1);
     assertThat(issues).isEmpty();
 
     // Filter by language
     issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2, emptyList(),
-      emptyList(), List.of("xoo"), null);
-    assertThat(issues).containsOnly("I1");
-
-    // Filter by resolved only
-    issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2, emptyList(),
-      emptyList(), emptyList(), true);
-    assertThat(issues).containsOnly("I1");
+      emptyList(), List.of("xoo"), 1);
+    assertThat(issues).contains("I1");
   }
 
 
@@ -256,7 +242,8 @@ public class IssueDaoTest {
     List<String> statusesB = List.of(STATUS_OPEN, STATUS_RESOLVED);
     IntStream.range(0, statusesB.size()).forEach(i -> insertBranchIssue(branchB, fileB, rule, "B" + i, statusesB.get(i), updatedAt));
 
-    List<IssueDto> branchAIssuesA1 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchA, "java", false, changedSince), 1);
+    List<IssueDto> branchAIssuesA1 = underTest.selectByBranch(db.getSession(), Set.of("issueA0", "issueA1", "issueA3"),
+      buildSelectByBranchQuery(branchA, "java", false, changedSince));
 
     assertThat(branchAIssuesA1)
       .extracting(IssueDto::getKey, IssueDto::getStatus)
@@ -266,13 +253,16 @@ public class IssueDaoTest {
         tuple("issueA3", STATUS_RESOLVED)
       );
 
-    List<IssueDto> branchAIssuesA2 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchA, "java", true, changedSince), 1);
+    List<IssueDto> branchAIssuesA2 = underTest.selectByBranch(db.getSession(), Set.of("issueA0", "issueA1", "issueA3"),
+      buildSelectByBranchQuery(branchA, "java", true, changedSince));
 
     assertThat(branchAIssuesA2)
       .extracting(IssueDto::getKey, IssueDto::getStatus)
-      .containsExactly(tuple("issueA3", STATUS_RESOLVED));
+      .containsExactly(tuple("issueA0", STATUS_OPEN),
+        tuple("issueA1", STATUS_REVIEWED),
+        tuple("issueA3", STATUS_RESOLVED));
 
-    List<IssueDto> branchBIssuesB1 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchB, "java", false, changedSince), 1);
+    List<IssueDto> branchBIssuesB1 = underTest.selectByBranch(db.getSession(), Set.of("issueB0", "issueB1"), buildSelectByBranchQuery(branchB, "java", false, changedSince));
 
     assertThat(branchBIssuesB1)
       .extracting(IssueDto::getKey, IssueDto::getStatus)
@@ -281,11 +271,12 @@ public class IssueDaoTest {
         tuple("issueB1", STATUS_RESOLVED)
       );
 
-    List<IssueDto> branchBIssuesB2 = underTest.selectByBranch(db.getSession(), buildSelectByBranchQuery(branchB, "java", true, changedSince), 1);
+    List<IssueDto> branchBIssuesB2 = underTest.selectByBranch(db.getSession(), Set.of("issueB0", "issueB1"), buildSelectByBranchQuery(branchB, "java", true, changedSince));
 
     assertThat(branchBIssuesB2)
       .extracting(IssueDto::getKey, IssueDto::getStatus)
-      .containsExactly(tuple("issueB1", STATUS_RESOLVED));
+      .containsExactly(tuple("issueB0", STATUS_OPEN),
+        tuple("issueB1", STATUS_RESOLVED));
   }
 
   @Test
index 7c8b5ce2b35793ee956bc724065cb51bd42b119c..c7cfe1503098f51037915c11aa3cb5f72b3b1788 100644 (file)
@@ -47,7 +47,6 @@ import static java.util.Collections.emptyList;
 import static org.sonar.api.web.UserRole.USER;
 
 public abstract class BasePullAction implements IssuesWsAction {
-
   protected static final String PROJECT_KEY_PARAM = "projectKey";
   protected static final String BRANCH_NAME_PARAM = "branchName";
   protected static final String LANGUAGES_PARAM = "languages";
@@ -162,9 +161,7 @@ public abstract class BasePullAction implements IssuesWsAction {
 
     var issuesRetriever = new PullActionIssuesRetriever(dbClient, queryParams);
 
-    Set<String> issueKeysSnapshot = new HashSet<>(getIssueKeysSnapshot(queryParams));
-    Consumer<List<IssueDto>> listConsumer = issueDtos -> pullActionResponseWriter.appendIssuesToResponse(issueDtos, outputStream);
-    issuesRetriever.processIssuesByBatch(dbSession, issueKeysSnapshot, listConsumer);
+    processNonClosedIssuesInBatches(dbSession, queryParams, outputStream, issuesRetriever);
 
     if (queryParams.getChangedSince() != null) {
       // in the "incremental mode" we need to send SonarLint also recently closed issues keys
@@ -178,6 +175,21 @@ public abstract class BasePullAction implements IssuesWsAction {
   protected abstract IssueQueryParams initializeQueryParams(BranchDto branchDto, @Nullable List<String> languages,
     @Nullable List<String> ruleRepositories, boolean resolvedOnly, @Nullable Long changedSince);
 
-  protected abstract Set<String> getIssueKeysSnapshot(IssueQueryParams queryParams);
+  protected abstract Set<String> getIssueKeysSnapshot(IssueQueryParams queryParams, int page);
+
+  private void processNonClosedIssuesInBatches(DbSession dbSession, IssueQueryParams queryParams, OutputStream outputStream,
+    PullActionIssuesRetriever issuesRetriever) {
+    int nextPage = 1;
+    do {
+      Set<String> issueKeysSnapshot = new HashSet<>(getIssueKeysSnapshot(queryParams, nextPage));
+      Consumer<List<IssueDto>> listConsumer = issueDtos -> pullActionResponseWriter.appendIssuesToResponse(issueDtos, outputStream);
+      issuesRetriever.processIssuesByBatch(dbSession, issueKeysSnapshot, listConsumer);
 
+      if (issueKeysSnapshot.isEmpty()) {
+        nextPage = -1;
+      } else {
+        nextPage++;
+      }
+    } while (nextPage > 0);
+  }
 }
index 234fd8eef53b2018f0f703f1267fd4f4d63fe6ff..b08e66e7c7a5e94b641bc718dcad924402148222 100644 (file)
@@ -55,19 +55,19 @@ public class PullAction extends BasePullAction {
   }
 
   @Override
-  protected Set<String> getIssueKeysSnapshot(IssueQueryParams issueQueryParams) {
+  protected Set<String> getIssueKeysSnapshot(IssueQueryParams issueQueryParams, int page) {
     try (DbSession dbSession = dbClient.openSession(false)) {
       Optional<Long> changedSinceDate = ofNullable(issueQueryParams.getChangedSince());
 
       if (changedSinceDate.isPresent()) {
         return dbClient.issueDao().selectIssueKeysByComponentUuidAndChangedSinceDate(dbSession, issueQueryParams.getBranchUuid(),
           changedSinceDate.get(), issueQueryParams.getRuleRepositories(), taintChecker.getTaintRepositories(),
-          issueQueryParams.getLanguages(), issueQueryParams.isResolvedOnly());
+          issueQueryParams.getLanguages(), page);
       }
 
       return dbClient.issueDao().selectIssueKeysByComponentUuid(dbSession, issueQueryParams.getBranchUuid(),
         issueQueryParams.getRuleRepositories(), taintChecker.getTaintRepositories(),
-        issueQueryParams.getLanguages(), issueQueryParams.isResolvedOnly(), true);
+        issueQueryParams.getLanguages(), page);
     }
   }
 
@@ -77,13 +77,11 @@ public class PullAction extends BasePullAction {
     return new IssueQueryParams(branchDto.getUuid(), languages, ruleRepositories, taintChecker.getTaintRepositories(), resolvedOnly, changedSince);
   }
 
-
   @Override
   protected void validateRuleRepositories(List<String> ruleRepositories) {
     checkArgument(ruleRepositories
       .stream()
       .filter(taintChecker.getTaintRepositories()::contains)
       .count() == 0, "Incorrect rule repositories list: it should only include repositories that define Issues, and no Taint Vulnerabilities");
-
   }
 }
index 218fcfb873cb044fc98b8c6797efc4b2a46c3322..eba794fd3efe13bb2c2f6859498126989565adde 100644 (file)
@@ -54,20 +54,19 @@ public class PullTaintAction extends BasePullAction {
   }
 
   @Override
-  protected Set<String> getIssueKeysSnapshot(IssueQueryParams issueQueryParams) {
+  protected Set<String> getIssueKeysSnapshot(IssueQueryParams issueQueryParams, int page) {
     Optional<Long> changedSinceDate = ofNullable(issueQueryParams.getChangedSince());
 
     try (DbSession dbSession = dbClient.openSession(false)) {
       if (changedSinceDate.isPresent()) {
         return dbClient.issueDao().selectIssueKeysByComponentUuidAndChangedSinceDate(dbSession, issueQueryParams.getBranchUuid(),
           changedSinceDate.get(), issueQueryParams.getRuleRepositories(), emptyList(),
-          issueQueryParams.getLanguages(), false);
+          issueQueryParams.getLanguages(), page);
       }
 
       return dbClient.issueDao().selectIssueKeysByComponentUuid(dbSession, issueQueryParams.getBranchUuid(),
         issueQueryParams.getRuleRepositories(),
-        emptyList(), issueQueryParams.getLanguages(),
-        issueQueryParams.isResolvedOnly(), true);
+        emptyList(), issueQueryParams.getLanguages(),page);
 
     }
   }
@@ -75,7 +74,7 @@ public class PullTaintAction extends BasePullAction {
   @Override
   protected IssueQueryParams initializeQueryParams(BranchDto branchDto, @Nullable List<String> languages,
     @Nullable List<String> ruleRepositories, boolean resolvedOnly, @Nullable Long changedSince) {
-    return new IssueQueryParams(branchDto.getUuid(), languages, taintChecker.getTaintRepositories(), emptyList(), resolvedOnly, changedSince);
+    return new IssueQueryParams(branchDto.getUuid(), languages, taintChecker.getTaintRepositories(), emptyList(), false, changedSince);
   }
 
   @Override
index 8ed3738514bd6e85f11d1d13051ae33f5a2e0db7..ea0a4fff7a746670c58d7cb24228877d59b05185 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.issue.ws.pull;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -28,6 +29,7 @@ import org.sonar.db.DbSession;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.issue.IssueQueryParams;
 
+import static java.util.stream.Collectors.toList;
 import static org.sonar.db.issue.IssueDao.DEFAULT_PAGE_SIZE;
 
 public class PullActionIssuesRetriever {
@@ -41,41 +43,46 @@ public class PullActionIssuesRetriever {
   }
 
   public void processIssuesByBatch(DbSession dbSession, Set<String> issueKeysSnapshot, Consumer<List<IssueDto>> listConsumer) {
-    int nextPage = 1;
-    boolean hasMoreIssues = true;
+    boolean hasMoreIssues = !issueKeysSnapshot.isEmpty();
+    long offset = 0;
+
+    List<IssueDto> issueDtos = new ArrayList<>();
 
     while (hasMoreIssues) {
-      List<IssueDto> issueDtos = nextOpenIssues(dbSession, nextPage);
-      listConsumer.accept(filterDuplicateIssues(issueDtos, issueKeysSnapshot));
-      nextPage++;
-      if (issueDtos.isEmpty() || issueDtos.size() < DEFAULT_PAGE_SIZE) {
-        hasMoreIssues = false;
-      }
+      Set<String> page = paginate(issueKeysSnapshot, offset);
+      issueDtos.addAll(filterIssues(nextOpenIssues(dbSession, page)));
+      offset += page.size();
+      hasMoreIssues = offset < issueKeysSnapshot.size();
     }
+
+    listConsumer.accept(issueDtos);
   }
 
-  private static List<IssueDto> filterDuplicateIssues(List<IssueDto> issues, Set<String> issueKeysSnapshot) {
+  private List<IssueDto> filterIssues(List<IssueDto> issues) {
     return issues
       .stream()
-      .filter(issue -> isUniqueIssue(issue.getKee(), issueKeysSnapshot))
-      .collect(Collectors.toList());
+      .filter(i -> hasCorrectTypeAndStatus(i, issueQueryParams))
+      .collect(toList());
   }
 
-  private static boolean isUniqueIssue(String issueKey, Set<String> issueKeysSnapshot) {
-    boolean isUniqueIssue = issueKeysSnapshot.contains(issueKey);
-
-    if (isUniqueIssue) {
-      issueKeysSnapshot.remove(issueKey);
-    }
-
-    return isUniqueIssue;
+  private static boolean hasCorrectTypeAndStatus(IssueDto issueDto, IssueQueryParams queryParams) {
+    return issueDto.getType() != 4 &&
+      (queryParams.isResolvedOnly() ? issueDto.getStatus().equals("RESOLVED") : true);
   }
 
   public List<String> retrieveClosedIssues(DbSession dbSession) {
     return dbClient.issueDao().selectRecentlyClosedIssues(dbSession, issueQueryParams);
   }
 
-  private List<IssueDto> nextOpenIssues(DbSession dbSession, int nextPage) {
-    return dbClient.issueDao().selectByBranch(dbSession, issueQueryParams, nextPage);
+  private List<IssueDto> nextOpenIssues(DbSession dbSession, Set<String> issueKeysSnapshot) {
+    return dbClient.issueDao().selectByBranch(dbSession, issueKeysSnapshot, issueQueryParams);
+  }
+
+  private static Set<String> paginate(Set<String> issueKeys, long offset) {
+    return issueKeys
+      .stream()
+      .skip(offset)
+      .limit(DEFAULT_PAGE_SIZE)
+      .collect(Collectors.toSet());
   }
 }
index 76046a4c40be26c23635684b50fd9845a2e6821b..38b725ba91402fdc91571dc1ad6ef702a002090a 100644 (file)
@@ -27,6 +27,7 @@ import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.sonar.db.DbClient;
 import org.sonar.db.issue.IssueDao;
 import org.sonar.db.issue.IssueDto;
@@ -34,8 +35,9 @@ import org.sonar.db.issue.IssueQueryParams;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class PullActionIssuesRetrieverTest {
@@ -46,7 +48,8 @@ public class PullActionIssuesRetrieverTest {
   private final List<String> ruleRepositories = List.of("js-security", "java");
   private final Long defaultChangedSince = 1_000_000L;
 
-  private final IssueQueryParams queryParams = new IssueQueryParams(branchUuid, languages, ruleRepositories, null, false, defaultChangedSince);
+  private final IssueQueryParams queryParams = new IssueQueryParams(branchUuid, languages, ruleRepositories, null,
+    false, defaultChangedSince);
   private final IssueDao issueDao = mock(IssueDao.class);
 
   @Before
@@ -57,7 +60,7 @@ public class PullActionIssuesRetrieverTest {
   @Test
   public void processIssuesByBatch_givenNoIssuesReturnedByDatabase_noIssuesConsumed() {
     var pullActionIssuesRetriever = new PullActionIssuesRetriever(dbClient, queryParams);
-    when(issueDao.selectByBranch(any(), any(), anyInt()))
+    when(issueDao.selectByBranch(any(), any(), any()))
       .thenReturn(List.of());
     List<IssueDto> returnedDtos = new ArrayList<>();
     Consumer<List<IssueDto>> listConsumer = returnedDtos::addAll;
@@ -72,7 +75,7 @@ public class PullActionIssuesRetrieverTest {
     var pullActionIssuesRetriever = new PullActionIssuesRetriever(dbClient, queryParams);
     List<IssueDto> thousandIssues = IntStream.rangeClosed(1, 1000).mapToObj(i -> new IssueDto().setKee(Integer.toString(i))).collect(Collectors.toList());
     IssueDto singleIssue = new IssueDto().setKee("kee");
-    when(issueDao.selectByBranch(any(), any(), anyInt()))
+    when(issueDao.selectByBranch(any(), any(), any()))
       .thenReturn(thousandIssues)
       .thenReturn(List.of(singleIssue));
     List<IssueDto> returnedDtos = new ArrayList<>();
@@ -82,33 +85,21 @@ public class PullActionIssuesRetrieverTest {
     thousandIssueUuidsSnapshot.add(singleIssue.getKee());
     pullActionIssuesRetriever.processIssuesByBatch(dbClient.openSession(true), thousandIssueUuidsSnapshot, listConsumer);
 
-    assertThat(returnedDtos).hasSize(1001);
-  }
+    ArgumentCaptor<Set<String>> uuidsCaptor = ArgumentCaptor.forClass(Set.class);
+    verify(issueDao, times(2)).selectByBranch(any(), uuidsCaptor.capture(), any());
+    List<Set<String>> capturedSets = uuidsCaptor.getAllValues();
+    assertThat(capturedSets.get(0)).hasSize(1000);
+    assertThat(capturedSets.get(1)).hasSize(1);
 
-  @Test
-  public void processIssuesByBatch_filter_out_duplicate_issue_entries() {
-    var pullActionIssuesRetriever = new PullActionIssuesRetriever(dbClient, queryParams);
-    IssueDto issue1 = new IssueDto().setKee("kee1");
-    IssueDto issue2 = new IssueDto().setKee("kee2");
-    List<IssueDto> issues = List.of(issue1, issue1, issue1, issue2);
-    when(issueDao.selectByBranch(any(), any(), anyInt()))
-      .thenReturn(issues);
-    List<IssueDto> returnedDtos = new ArrayList<>();
-    Consumer<List<IssueDto>> listConsumer = returnedDtos::addAll;
-
-    Set<String> thousandIssueKeysSnapshot = issues.stream().map(IssueDto::getKee).collect(Collectors.toSet());
-    pullActionIssuesRetriever.processIssuesByBatch(dbClient.openSession(true), thousandIssueKeysSnapshot, listConsumer);
-
-    assertThat(returnedDtos)
-      .hasSize(2)
-      .containsExactlyInAnyOrder(issue1, issue2);
+    assertThat(returnedDtos).hasSize(1001);
   }
 
   @Test
   public void processIssuesByBatch_correctly_processes_all_issues_regardless_of_creation_timestamp() {
     var pullActionIssuesRetriever = new PullActionIssuesRetriever(dbClient, queryParams);
-    List<IssueDto> issuesWithSameCreationTimestamp = IntStream.rangeClosed(1, 100).mapToObj(i -> new IssueDto().setKee(Integer.toString(i)).setCreatedAt(100L)).collect(Collectors.toList());
-    when(issueDao.selectByBranch(any(), any(), anyInt()))
+    List<IssueDto> issuesWithSameCreationTimestamp = IntStream.rangeClosed(1, 100).mapToObj(i -> new IssueDto()
+      .setKee(Integer.toString(i)).setCreatedAt(100L)).collect(Collectors.toList());
+    when(issueDao.selectByBranch(any(), any(), any()))
       .thenReturn(issuesWithSameCreationTimestamp);
     List<IssueDto> returnedDtos = new ArrayList<>();
     Consumer<List<IssueDto>> listConsumer = returnedDtos::addAll;