]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19324 Fix api/projects/search low performances
authorAlain Kermis <alain.kermis@sonarsource.com>
Thu, 24 Aug 2023 06:35:56 +0000 (08:35 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 29 Aug 2023 20:03:09 +0000 (20:03 +0000)
16 files changed:
server/sonar-db-dao/src/it/java/org/sonar/db/component/ComponentDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/component/SnapshotMapper.xml
server/sonar-db-dao/src/schema/schema-sq.ddl
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentQueryTest.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranches.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/DbVersion102.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranchesTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranchesTest/schema.sql [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/ce/ws/ActivityAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/template/BulkApplyTemplateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchAction.java

index 2299f88c60f0d3d64c5db233a87812df9bd62e7e..c3d4d5097cd68e5935bbd78e3def38b6b260b4f3 100644 (file)
@@ -44,6 +44,7 @@ import org.sonar.api.resources.Scopes;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.Pagination;
 import org.sonar.db.RowNotFoundException;
 import org.sonar.db.audit.AuditPersister;
 import org.sonar.db.audit.NoOpAuditPersister;
@@ -79,6 +80,7 @@ import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.resources.Qualifiers.SUBVIEW;
 import static org.sonar.api.resources.Qualifiers.VIEW;
 import static org.sonar.api.utils.DateUtils.parseDate;
+import static org.sonar.db.Pagination.forPage;
 import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
 import static org.sonar.db.component.BranchType.BRANCH;
 import static org.sonar.db.component.BranchType.PULL_REQUEST;
@@ -931,35 +933,35 @@ public class ComponentDaoIT {
     SnapshotDto analyzedPortfolio = db.components().insertProjectAndSnapshot(ComponentTesting.newPortfolio());
 
     Supplier<ComponentQuery.Builder> query = () -> ComponentQuery.builder().setQualifiers(PROJECT).setOnProvisionedOnly(true);
-    assertThat(underTest.selectByQuery(dbSession, query.get().build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().build(), forPage(1).andSize(10)))
       .extracting(ComponentDto::uuid)
       .containsOnly(provisionedProject.uuid());
 
     // pagination
-    assertThat(underTest.selectByQuery(dbSession, query.get().build(), 2, 10)).isEmpty();
+    assertThat(underTest.selectByQuery(dbSession, query.get().build(), forPage(3).andSize(10))).isEmpty();
 
     // filter on qualifiers
-    assertThat(underTest.selectByQuery(dbSession, query.get().setQualifiers("XXX").build(), 0, 10)).isEmpty();
-    assertThat(underTest.selectByQuery(dbSession, query.get().setQualifiers(PROJECT, "XXX").build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().setQualifiers("XXX").build(), forPage(1).andSize(10))).isEmpty();
+    assertThat(underTest.selectByQuery(dbSession, query.get().setQualifiers(PROJECT, "XXX").build(), forPage(1).andSize(10)))
       .extracting(ComponentDto::uuid)
       .containsOnly(provisionedProject.uuid());
-    assertThat(underTest.selectByQuery(dbSession, query.get().setQualifiers(PROJECT, Qualifiers.VIEW).build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().setQualifiers(PROJECT, Qualifiers.VIEW).build(), forPage(1).andSize(10)))
       .extracting(ComponentDto::uuid)
       .containsOnly(provisionedProject.uuid(), provisionedPortfolio.uuid());
 
     // match key
-    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery(provisionedProject.getKey()).build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery(provisionedProject.getKey()).build(), forPage(1).andSize(10)))
       .extracting(ComponentDto::uuid)
       .containsExactly(provisionedProject.uuid());
-    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("pROvisiONed.proJEcT").setPartialMatchOnKey(true).build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("pROvisiONed.proJEcT").setPartialMatchOnKey(true).build(), forPage(1).andSize(10)))
       .extracting(ComponentDto::uuid)
       .containsExactly(provisionedProject.uuid());
-    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("missing").setPartialMatchOnKey(true).build(), 0, 10)).isEmpty();
-    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("to be escaped '\"\\%").setPartialMatchOnKey(true).build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("missing").setPartialMatchOnKey(true).build(), forPage(1).andSize(10))).isEmpty();
+    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("to be escaped '\"\\%").setPartialMatchOnKey(true).build(), forPage(1).andSize(10)))
       .isEmpty();
 
     // match name
-    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("ned proj").setPartialMatchOnKey(true).build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().setNameOrKeyQuery("ned proj").setPartialMatchOnKey(true).build(), forPage(1).andSize(10)))
       .extracting(ComponentDto::uuid)
       .containsExactly(provisionedProject.uuid());
   }
@@ -972,7 +974,7 @@ public class ComponentDaoIT {
 
     // the project does not have any analysis
     ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
-    assertThat(underTest.selectByQuery(dbSession, query.get().build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().build(), forPage(1).andSize(10)))
       .extracting(ComponentDto::uuid)
       .containsOnly(project.uuid());
 
@@ -981,7 +983,7 @@ public class ComponentDaoIT {
     ComponentDto branchWithoutAnalysis = db.components().insertProjectBranch(project);
     ComponentDto branchWithAnalysis = db.components().insertProjectBranch(project);
     db.components().insertSnapshot(branchWithAnalysis);
-    assertThat(underTest.selectByQuery(dbSession, query.get().build(), 0, 10))
+    assertThat(underTest.selectByQuery(dbSession, query.get().build(), forPage(1).andSize(10)))
       .isEmpty();
   }
 
@@ -999,7 +1001,7 @@ public class ComponentDaoIT {
       .setQualifiers(PROJECT)
       .setOnProvisionedOnly(true);
 
-    List<ComponentDto> results = underTest.selectByQuery(dbSession, query.get().build(), 0, 10);
+    List<ComponentDto> results = underTest.selectByQuery(dbSession, query.get().build(), forPage(1).andSize(10));
     assertThat(results)
       .extracting(ComponentDto::uuid)
       .containsExactly(
@@ -1172,7 +1174,8 @@ public class ComponentDaoIT {
 
   private void assertThatSelectByQueryThrowsIAE(ComponentQuery.Builder query, String expectedMessage) {
     ComponentQuery componentQuery = query.build();
-    assertThatThrownBy(() -> underTest.selectByQuery(dbSession, componentQuery, 0, Integer.MAX_VALUE))
+    Pagination pagination = forPage(1).andSize(Integer.MAX_VALUE);
+    assertThatThrownBy(() -> underTest.selectByQuery(dbSession, componentQuery, pagination))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessage(expectedMessage);
   }
@@ -1186,12 +1189,12 @@ public class ComponentDaoIT {
     }
 
     ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("oJect").setQualifiers(PROJECT).build();
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 1, 3);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, forPage(2).andSize(3));
     int count = underTest.countByQuery(dbSession, query);
 
     assertThat(result).hasSize(3);
     assertThat(count).isEqualTo(9);
-    assertThat(result).extracting(ComponentDto::name).containsExactly("project-2", "project-3", "project-4");
+    assertThat(result).extracting(ComponentDto::name).containsExactly("project-4", "project-5", "project-6");
   }
 
   @Test
@@ -1199,8 +1202,8 @@ public class ComponentDaoIT {
     ComponentDto main = db.components().insertPublicProject().getMainBranchComponent();
     ComponentDto branch = db.components().insertProjectBranch(main);
 
-    assertThat(underTest.selectByQuery(dbSession, ALL_PROJECTS_COMPONENT_QUERY, 0, 2)).hasSize(1);
-    assertThat(underTest.selectByQuery(dbSession, ALL_PROJECTS_COMPONENT_QUERY, 0, 2).get(0).uuid()).isEqualTo(main.uuid());
+    assertThat(underTest.selectByQuery(dbSession, ALL_PROJECTS_COMPONENT_QUERY, forPage(1).andSize(2))).hasSize(1);
+    assertThat(underTest.selectByQuery(dbSession, ALL_PROJECTS_COMPONENT_QUERY, forPage(1).andSize(2)).get(0).uuid()).isEqualTo(main.uuid());
   }
 
   @Test
@@ -1216,7 +1219,7 @@ public class ComponentDaoIT {
     db.components().insertProjectAndSnapshot(newPrivateProjectDto().setName("project-\\_%/-name"));
 
     ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("-\\_%/-").setQualifiers(PROJECT).build();
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, forPage(1).andSize(10));
 
     assertThat(result).hasSize(1);
     assertThat(result.get(0).name()).isEqualTo("project-\\_%/-name");
@@ -1228,7 +1231,7 @@ public class ComponentDaoIT {
     db.components().insertProjectAndSnapshot(newPrivateProjectDto().setKey("project-key-that-does-not-match"));
 
     ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("project-_%-key").setQualifiers(PROJECT).build();
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, forPage(1).andSize(10));
 
     assertThat(result).hasSize(1);
     assertThat(result.get(0).getKey()).isEqualTo("project-_%-key");
@@ -1242,7 +1245,7 @@ public class ComponentDaoIT {
       .setNameOrKeyQuery("JECT-K")
       .setPartialMatchOnKey(true)
       .setQualifiers(PROJECT).build();
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, forPage(1).andSize(10));
 
     assertThat(result).hasSize(1);
     assertThat(result.get(0).getKey()).isEqualTo("project-key");
@@ -1267,19 +1270,11 @@ public class ComponentDaoIT {
       .containsExactlyInAnyOrder(oldProject.uuid(), recentProject.uuid());
 
     // before date on any branch
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedBefore(recentTime)))
+    assertThat(selectProjectUuidsByQuery(q -> q.setAllBranchesAnalyzedBefore(recentTime)))
       .containsExactlyInAnyOrder(oldProject.uuid());
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedBefore(aLongTimeAgo)))
+    assertThat(selectProjectUuidsByQuery(q -> q.setAllBranchesAnalyzedBefore(aLongTimeAgo)))
       .isEmpty();
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedBefore(recentTime + 1_000L)))
-      .containsExactlyInAnyOrder(oldProject.uuid(), recentProject.uuid());
-
-    // after date
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedAfter(recentTime - 1_000L)))
-      .containsExactlyInAnyOrder(recentProject.uuid());
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedAfter(recentTime + 1_000L)))
-      .isEmpty();
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedAfter(aLongTimeAgo)))
+    assertThat(selectProjectUuidsByQuery(q -> q.setAllBranchesAnalyzedBefore(recentTime + 1_000L)))
       .containsExactlyInAnyOrder(oldProject.uuid(), recentProject.uuid());
   }
 
@@ -1306,19 +1301,11 @@ public class ComponentDaoIT {
     assertThat(selectProjectUuidsByQuery(q -> q.setAnalyzedBefore(recentTime + 1_000L))).isEmpty();
 
     // before date on any branch
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedBefore(recentTime)))
+    assertThat(selectProjectUuidsByQuery(q -> q.setAllBranchesAnalyzedBefore(recentTime)))
       .containsExactlyInAnyOrder(oldProject.uuid());
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedBefore(aLongTimeAgo)))
-      .isEmpty();
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedBefore(recentTime + 1_000L)))
-      .containsExactlyInAnyOrder(oldProject.uuid(), recentProject.uuid());
-
-    // after date
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedAfter(recentTime - 1_000L)))
-      .containsExactlyInAnyOrder(recentProject.uuid());
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedAfter(recentTime + 1_000L)))
+    assertThat(selectProjectUuidsByQuery(q -> q.setAllBranchesAnalyzedBefore(aLongTimeAgo)))
       .isEmpty();
-    assertThat(selectProjectUuidsByQuery(q -> q.setAnyBranchAnalyzedAfter(aLongTimeAgo)))
+    assertThat(selectProjectUuidsByQuery(q -> q.setAllBranchesAnalyzedBefore(recentTime + 1_000L)))
       .containsExactlyInAnyOrder(oldProject.uuid(), recentProject.uuid());
   }
 
@@ -1387,7 +1374,7 @@ public class ComponentDaoIT {
   private List<String> selectUuidsByQuery(String qualifier, Consumer<ComponentQuery.Builder> query) {
     ComponentQuery.Builder builder = ComponentQuery.builder().setQualifiers(qualifier);
     query.accept(builder);
-    return underTest.selectByQuery(dbSession, builder.build(), 0, 5)
+    return underTest.selectByQuery(dbSession, builder.build(), forPage(1).andSize(5))
       .stream()
       .map(ComponentDto::uuid)
       .toList();
@@ -1402,9 +1389,9 @@ public class ComponentDaoIT {
     ComponentQuery publicProjectsQuery = ComponentQuery.builder().setPrivate(false).setQualifiers(PROJECT).build();
     ComponentQuery allProjectsQuery = ComponentQuery.builder().setPrivate(null).setQualifiers(PROJECT).build();
 
-    assertThat(underTest.selectByQuery(dbSession, privateProjectsQuery, 0, 10)).extracting(ComponentDto::getKey).containsExactly("private-key");
-    assertThat(underTest.selectByQuery(dbSession, publicProjectsQuery, 0, 10)).extracting(ComponentDto::getKey).containsExactly("public-key");
-    assertThat(underTest.selectByQuery(dbSession, allProjectsQuery, 0, 10)).extracting(ComponentDto::getKey).containsOnly("public-key", "private-key");
+    assertThat(underTest.selectByQuery(dbSession, privateProjectsQuery, forPage(1).andSize(10))).extracting(ComponentDto::getKey).containsExactly("private-key");
+    assertThat(underTest.selectByQuery(dbSession, publicProjectsQuery, forPage(1).andSize(10))).extracting(ComponentDto::getKey).containsExactly("public-key");
+    assertThat(underTest.selectByQuery(dbSession, allProjectsQuery, forPage(1).andSize(10))).extracting(ComponentDto::getKey).containsOnly("public-key", "private-key");
   }
 
   @Test
@@ -1412,7 +1399,7 @@ public class ComponentDaoIT {
     db.components().insertPrivateProject().getMainBranchComponent();
     ComponentQuery dbQuery = ComponentQuery.builder().setQualifiers(PROJECT).setComponentKeys(emptySet()).build();
 
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, dbQuery, 0, 10);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, dbQuery, forPage(1).andSize(10));
     int count = underTest.countByQuery(dbSession, dbQuery);
 
     assertThat(result).isEmpty();
@@ -1427,7 +1414,7 @@ public class ComponentDaoIT {
     ComponentQuery query = ComponentQuery.builder().setQualifiers(PROJECT)
       .setComponentKeys(newHashSet(sonarqube.getKey(), jdk8.getKey())).build();
 
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, forPage(1).andSize(10));
 
     assertThat(result).hasSize(2).extracting(ComponentDto::getKey)
       .containsExactlyInAnyOrder(sonarqube.getKey(), jdk8.getKey())
@@ -1439,7 +1426,7 @@ public class ComponentDaoIT {
     db.components().insertPrivateProject().getMainBranchComponent();
     ComponentQuery dbQuery = ComponentQuery.builder().setQualifiers(PROJECT).setComponentUuids(emptySet()).build();
 
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, dbQuery, 0, 10);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, dbQuery, forPage(1).andSize(10));
     int count = underTest.countByQuery(dbSession, dbQuery);
 
     assertThat(result).isEmpty();
@@ -1454,7 +1441,7 @@ public class ComponentDaoIT {
     ComponentQuery query = ComponentQuery.builder().setQualifiers(PROJECT)
       .setComponentUuids(newHashSet(sonarqube.uuid(), jdk8.uuid())).build();
 
-    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, forPage(1).andSize(10));
 
     assertThat(result).hasSize(2).extracting(ComponentDto::uuid)
       .containsOnlyOnce(sonarqube.uuid(), jdk8.uuid())
index a78e4545a75b2363bf90241f84a5f0cf98cd601f..0d64637e9e4621419406112c17f86931e7968392 100644 (file)
@@ -31,10 +31,10 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import org.apache.ibatis.session.ResultHandler;
-import org.apache.ibatis.session.RowBounds;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
+import org.sonar.db.Pagination;
 import org.sonar.db.RowNotFoundException;
 import org.sonar.db.audit.AuditPersister;
 import org.sonar.db.audit.model.ComponentNewValue;
@@ -104,8 +104,8 @@ public class ComponentDao implements Dao {
    * @throws IllegalArgumentException if parameter query#getComponentKeys() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
    * @throws IllegalArgumentException if parameter query#getMainComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
    */
-  public List<ComponentDto> selectByQuery(DbSession dbSession, ComponentQuery query, int offset, int limit) {
-    return selectByQueryImpl(dbSession, query, offset, limit);
+  public List<ComponentDto> selectByQuery(DbSession dbSession, ComponentQuery query, Pagination pagination) {
+    return selectByQueryImpl(dbSession, query, pagination);
   }
 
   /**
@@ -117,12 +117,12 @@ public class ComponentDao implements Dao {
     return countByQueryImpl(session, query);
   }
 
-  private static List<ComponentDto> selectByQueryImpl(DbSession session, ComponentQuery query, int offset, int limit) {
+  private static List<ComponentDto> selectByQueryImpl(DbSession session, ComponentQuery query, Pagination pagination) {
     if (query.hasEmptySetOfComponents()) {
       return emptyList();
     }
     checkThatNotTooManyComponents(query);
-    return mapper(session).selectByQuery(query, new RowBounds(offset, limit));
+    return mapper(session).selectByQuery(query, pagination);
   }
 
   private static int countByQueryImpl(DbSession session, ComponentQuery query) {
index 25424e7534944536eeba9b42753bf6a6d3bfc956..e09c48a58f930d024c202853668607662158f7d8 100644 (file)
@@ -26,7 +26,7 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.session.ResultHandler;
-import org.apache.ibatis.session.RowBounds;
+import org.sonar.db.Pagination;
 
 public interface ComponentMapper {
   @CheckForNull
@@ -54,7 +54,7 @@ public interface ComponentMapper {
 
   List<ComponentDto> selectComponentsByQualifiers(@Param("qualifiers") Collection<String> qualifiers);
 
-  List<ComponentDto> selectByQuery(@Param("query") ComponentQuery query, RowBounds rowBounds);
+  List<ComponentDto> selectByQuery(@Param("query") ComponentQuery query, @Param("pagination") Pagination pagination);
 
   int countByQuery(@Param("query") ComponentQuery query);
 
index 7baf5d3c409add09cfcb03fefc450e12bd9ff6d5..507cd6917005267679911ce666447925e7268c9a 100644 (file)
@@ -38,8 +38,6 @@ public class ComponentQuery {
   private final Set<String> componentUuids;
   private final Set<String> componentKeys;
   private final Long analyzedBefore;
-  private final Long anyBranchAnalyzedBefore;
-  private final Long anyBranchAnalyzedAfter;
   private final Long allBranchesAnalyzedBefore;
   private final Date createdAfter;
   private final boolean onProvisionedOnly;
@@ -52,8 +50,6 @@ public class ComponentQuery {
     this.componentKeys = builder.componentKeys;
     this.isPrivate = builder.isPrivate;
     this.analyzedBefore = builder.analyzedBefore;
-    this.anyBranchAnalyzedBefore = builder.anyBranchAnalyzedBefore;
-    this.anyBranchAnalyzedAfter = builder.anyBranchAnalyzedAfter;
     this.allBranchesAnalyzedBefore = builder.allBranchesAnalyzedBefore;
     this.createdAfter = builder.createdAfter;
     this.onProvisionedOnly = builder.onProvisionedOnly;
@@ -103,16 +99,6 @@ public class ComponentQuery {
     return analyzedBefore;
   }
 
-  @CheckForNull
-  public Long getAnyBranchAnalyzedBefore() {
-    return anyBranchAnalyzedBefore;
-  }
-
-  @CheckForNull
-  public Long getAnyBranchAnalyzedAfter() {
-    return anyBranchAnalyzedAfter;
-  }
-
   @CheckForNull
   public Long getAllBranchesAnalyzedBefore() {
     return allBranchesAnalyzedBefore;
@@ -144,8 +130,6 @@ public class ComponentQuery {
     private Set<String> componentUuids;
     private Set<String> componentKeys;
     private Long analyzedBefore;
-    private Long anyBranchAnalyzedBefore;
-    private Long anyBranchAnalyzedAfter;
     private Long allBranchesAnalyzedBefore;
     private Date createdAfter;
     private boolean onProvisionedOnly = false;
@@ -193,21 +177,6 @@ public class ComponentQuery {
       return this;
     }
 
-    /**
-     * Filter on date of last analysis. On projects, all branches and pull requests are taken into
-     * account. For example the analysis of a branch is included in the filter
-     * even if the main branch has never been analyzed.
-     */
-    public Builder setAnyBranchAnalyzedAfter(@Nullable Long l) {
-      this.anyBranchAnalyzedAfter = l;
-      return this;
-    }
-
-    public Builder setAnyBranchAnalyzedBefore(@Nullable Long l) {
-      this.anyBranchAnalyzedBefore = l;
-      return this;
-    }
-
     public Builder setCreatedAfter(@Nullable Date l) {
       this.createdAfter = l;
       return this;
index e55f64f3fde694dfa355913c94abd80d6062bea2..6dffe0b6848a5c7196e8ab5b069d7d4ef151a12f 100644 (file)
   </select>
 
   <select id="selectByQuery" resultType="Component">
+    <include refid="mainBranchAndPortfoliosWith"/>
     select
       <include refid="componentColumns"/>
     <include refid="sqlSelectByQuery"/>
     ORDER BY LOWER(p.name), p.name, p.created_at
+    offset (#{pagination.startRowNumber,jdbcType=INTEGER}-1) rows fetch next #{pagination.pageSize,jdbcType=INTEGER} rows only
   </select>
 
   <select id="countByQuery" resultType="int">
+    <include refid="mainBranchAndPortfoliosWith"/>
     select count(p.uuid)
     <include refid="sqlSelectByQuery"/>
   </select>
 
+  <sql id="mainBranchAndPortfoliosWith">
+    WITH main_branch_and_portfolios AS (SELECT p.uuid as projectUuid, p.kee as kee, p.uuid
+      from portfolios p
+      UNION
+      SELECT pb.project_uuid as projectUuid, p.kee as kee, pb.uuid
+      FROM project_branches pb
+      INNER JOIN projects p on pb.project_uuid = p.uuid
+      where pb.is_main = ${_true}
+    )
+  </sql>
+
   <sql id="sqlSelectByQuery">
-    from components p
+    from main_branch_and_portfolios mbp
+      INNER JOIN components p on mbp.kee = p.kee and p.branch_uuid = mbp.uuid
     <if test="query.analyzedBefore!=null">
       inner join snapshots sa on sa.root_component_uuid=p.uuid
         and sa.status='P' and sa.islast=${_true} and sa.created_at &lt; #{query.analyzedBefore,jdbcType=BIGINT}
     left join project_branches pb on pb.uuid = p.branch_uuid
     where
       p.enabled=${_true}
-      AND <include refid="mainBranchOrPortfolio"/>
-      AND p.copy_component_uuid is null
       <if test="query.qualifiers!=null">
         and p.qualifier in
           <foreach collection="query.qualifiers" item="qualifier" open="(" close=")" separator=",">
           where pb2.project_uuid = pb.project_uuid
         )
       </if>
-      <if test="query.anyBranchAnalyzedAfter != null">
-        and (
-          exists(
-            -- branches of projects and applications
-            select 1 from snapshots s
-            inner join project_branches pb2 on s.root_component_uuid = pb2.uuid
-            where pb2.project_uuid = pb.project_uuid
-            and s.status='P'
-            and s.islast = ${_true}
-            and s.created_at &gt;= #{query.anyBranchAnalyzedAfter,jdbcType=BIGINT}
-          )
-          or exists (
-            -- portfolios
-            select 1 from snapshots s
-            where s.root_component_uuid = p.uuid
-            and s.status='P'
-            and s.islast = ${_true}
-            and s.created_at &gt;= #{query.anyBranchAnalyzedAfter,jdbcType=BIGINT}
-          )
-        )
-      </if>
-      <if test="query.anyBranchAnalyzedBefore != null">
-        and (
-          exists(
-          -- branches of projects and applications
-          select 1 from snapshots s
-          inner join project_branches pb2 on s.root_component_uuid = pb2.uuid
-          where pb2.project_uuid = pb.project_uuid
-          and s.status='P'
-          and s.islast = ${_true}
-          and s.created_at &lt; #{query.anyBranchAnalyzedBefore,jdbcType=BIGINT}
-          )
-          or exists (
-          -- portfolios
-          select 1 from snapshots s
-          where s.root_component_uuid = p.uuid
-          and s.status='P'
-          and s.islast = ${_true}
-          and s.created_at &lt; #{query.anyBranchAnalyzedBefore,jdbcType=BIGINT}
-          )
-        )
-      </if>
       <if test="query.allBranchesAnalyzedBefore != null">
         and
         (
index 259d9da533abc3e15b7209b891fd846dbd5cbf15..5a2356a79aba0f7cfc3d2ecd1656d2aac180849f 100644 (file)
       and coalesce(pb.project_uuid, p.branch_uuid) = #{projectUuid,jdbcType=VARCHAR}
   </select>
 
-    <select id="selectLastAnalysisDateByProjectUuids" resultType="org.sonar.db.component.ProjectLastAnalysisDateDto">
+  <select id="selectLastAnalysisDateByProjectUuids" resultType="org.sonar.db.component.ProjectLastAnalysisDateDto">
     select
-      result_with_duplicates.project_uuid as project_uuid ,
+      result_with_duplicates.project_uuid as project_uuid,
       max(result_with_duplicates.last_analysis_date) as last_analysis_date
-    from
+    FROM
       (
-      select
-        coalesce(pb.project_uuid, c.branch_uuid) as project_uuid,
-        s.created_at as last_analysis_date
-      from
-        snapshots s
-      inner join components c on s.root_component_uuid = c.branch_uuid
-      left join project_branches pb on pb.uuid = c.branch_uuid
-      where
-        s.islast = ${_true}
-        and (
-      <!-- case of an analysis of a project or app, with entries in project_branches -->
-      (pb.uuid is not null and pb.project_uuid in
-            <foreach collection="projectUuids" item="projectUuid" separator="," open="(" close=")">
-              #{projectUuid,jdbcType=VARCHAR}
-            </foreach>)
-          or
-      <!-- case of an analysis of a portfolio, where there are no branches -->
-      (pb.uuid is null and c.branch_uuid in
-            <foreach collection="projectUuids" item="projectUuid" separator="," open="(" close=")">
-              #{projectUuid,jdbcType=VARCHAR}
-            </foreach>)
-        )
+      <!-- cases of an analysis of a project or app, with entries in project_branches -->
+      SELECT project_uuid, last_analysis_date
+      FROM (
+        SELECT
+          COALESCE(pb.project_uuid, c.branch_uuid) AS project_uuid,
+          s.created_at AS last_analysis_date
+        FROM snapshots s
+          INNER JOIN components c ON s.root_component_uuid = c.branch_uuid
+          LEFT JOIN project_branches pb ON pb.uuid = c.branch_uuid
+        WHERE
+          s.islast = ${_true}
+          AND pb.uuid IS NOT NULL
+          AND pb.project_uuid IN
+          <foreach collection="projectUuids" item="projectUuid" separator="," open="(" close=")">
+            #{projectUuid,jdbcType=VARCHAR}
+          </foreach>
+      ) project_case
+
+      UNION
+
+      <!-- cases of an analysis of a portfolio, where there are no branches -->
+      SELECT project_uuid, last_analysis_date
+      FROM (
+        SELECT
+          COALESCE(pb.project_uuid, c.branch_uuid) AS project_uuid,
+          s.created_at AS last_analysis_date
+        FROM snapshots s
+          INNER JOIN components c ON s.root_component_uuid = c.branch_uuid
+          LEFT JOIN project_branches pb ON pb.uuid = c.branch_uuid
+        WHERE
+          s.islast = ${_true}
+          AND pb.branch_type IS NULL
+          AND pb.uuid IS NULL
+          AND c.branch_uuid IN
+          <foreach collection="projectUuids" item="projectUuid" separator="," open="(" close=")">
+            #{projectUuid,jdbcType=VARCHAR}
+          </foreach>
+      ) portfolio_case
     ) result_with_duplicates
-    group by
-      result_with_duplicates.project_uuid
+    GROUP BY project_uuid
   </select>
+
   <select id="selectLastSnapshotByRootComponentUuid" resultType="Snapshot">
     select <include refid="snapshotColumns" />
     from snapshots s
index 7944ece37b2897fa9d2252feadc88702413eddeb..5721307e3f7828d7b6bc5da9a2197748e62f6fb2 100644 (file)
@@ -690,6 +690,7 @@ CREATE TABLE "PROJECT_BRANCHES"(
 );
 ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID");
 CREATE UNIQUE INDEX "UNIQ_PROJECT_BRANCHES" ON "PROJECT_BRANCHES"("BRANCH_TYPE" NULLS FIRST, "PROJECT_UUID" NULLS FIRST, "KEE" NULLS FIRST);
+CREATE INDEX "PROJECT_BRANCHES_PROJECT_UUID" ON "PROJECT_BRANCHES"("PROJECT_UUID" NULLS FIRST);
 
 CREATE TABLE "PROJECT_LINKS"(
     "UUID" CHARACTER VARYING(40) NOT NULL,
index ced666d7df6fa1738df2bf568aba434e2f4bd221..bef9bf628955210d826e3bef8f2d2ca5499f46e3 100644 (file)
@@ -36,16 +36,14 @@ public class ComponentQueryTest {
   public void build_query() {
     ComponentQuery underTest = ComponentQuery.builder()
       .setNameOrKeyQuery("key")
-      .setAnyBranchAnalyzedBefore(100L)
-      .setAnyBranchAnalyzedAfter(200L)
+      .setAllBranchesAnalyzedBefore(100L)
       .setCreatedAfter(new Date(300L))
       .setQualifiers(PROJECT)
       .build();
 
     assertThat(underTest.getNameOrKeyQuery()).isEqualTo("key");
     assertThat(underTest.getQualifiers()).containsOnly(PROJECT);
-    assertThat(underTest.getAnyBranchAnalyzedBefore()).isEqualTo(100L);
-    assertThat(underTest.getAnyBranchAnalyzedAfter()).isEqualTo(200L);
+    assertThat(underTest.getAllBranchesAnalyzedBefore()).isEqualTo(100L);
     assertThat(underTest.getCreatedAfter().getTime()).isEqualTo(300L);
     assertThat(underTest.isOnProvisionedOnly()).isFalse();
     assertThat(underTest.isPartialMatchOnKey()).isFalse();
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranches.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranches.java
new file mode 100644 (file)
index 0000000..552b071
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v102;
+
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.CreateIndexOnColumn;
+
+class CreateIndexProjectUuidInProjectBranches extends CreateIndexOnColumn {
+
+  private static final String TABLE_NAME = "project_branches";
+  private static final String COLUMN_NAME = "project_uuid";
+
+  public CreateIndexProjectUuidInProjectBranches(Database db) {
+    super(db, TABLE_NAME, COLUMN_NAME, false);
+  }
+}
index 2ee6cb2a90d8085f6ae9b3ce40f94a6f8e916552..60d608993dd43253a047b9f8891fddfcafdd834b 100644 (file)
@@ -25,7 +25,6 @@ import org.sonar.server.platform.db.migration.version.DbVersion;
 // ignoring bad number formatting, as it's indented that we align the migration numbers to SQ versions
 @SuppressWarnings("java:S3937")
 public class DbVersion102 implements DbVersion {
-
   /**
    * We use the start of the 10.X cycle as an opportunity to align migration numbers with the SQ version number.
    * Please follow this pattern:
@@ -98,6 +97,7 @@ public class DbVersion102 implements DbVersion {
       .add(10_2_043, "Create 'previous_non_compliant_value' in 'new_code_periods' table", CreatePreviousNonCompliantValueInNewCodePeriods.class)
       .add(10_2_044, "Update column 'value' and populate column 'previous_non_compliant_value' in 'new_code_periods' table",
         UpdateValueAndPopulatePreviousNonCompliantValueInNewCodePeriods.class)
-      .add(10_2_045, "Alter 'project_uuid' in 'user_dismissed_messages' - make it nullable", MakeProjectUuidNullableInUserDismissedMessages.class);
+      .add(10_2_045, "Alter 'project_uuid' in 'user_dismissed_messages' - make it nullable", MakeProjectUuidNullableInUserDismissedMessages.class)
+      .add(10_2_046, "Create index 'project_branches_project_uuid' in 'project_branches' table", CreateIndexProjectUuidInProjectBranches.class);
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranchesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranchesTest.java
new file mode 100644 (file)
index 0000000..bb8a50d
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v102;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+public class CreateIndexProjectUuidInProjectBranchesTest {
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(CreateIndexProjectUuidInProjectBranchesTest.class, "schema.sql");
+
+  private final CreateIndexProjectUuidInProjectBranches createIndex = new CreateIndexProjectUuidInProjectBranches(db.database());
+
+  @Test
+  public void migration_should_create_index() throws SQLException {
+    db.assertIndexDoesNotExist("project_branches", "project_branches_project_uuid");
+
+    createIndex.execute();
+
+    db.assertIndex("project_branches", "project_branches_project_uuid", "project_uuid");
+  }
+
+  @Test
+  public void migration_should_be_reentrant() throws SQLException {
+    createIndex.execute();
+    createIndex.execute();
+
+    db.assertIndex("project_branches", "project_branches_project_uuid", "project_uuid");
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranchesTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v102/CreateIndexProjectUuidInProjectBranchesTest/schema.sql
new file mode 100644 (file)
index 0000000..4b20881
--- /dev/null
@@ -0,0 +1,15 @@
+CREATE TABLE "PROJECT_BRANCHES"(
+    "UUID" CHARACTER VARYING(50) NOT NULL,
+    "PROJECT_UUID" CHARACTER VARYING(50) NOT NULL,
+    "KEE" CHARACTER VARYING(255) NOT NULL,
+    "BRANCH_TYPE" CHARACTER VARYING(12) NOT NULL,
+    "MERGE_BRANCH_UUID" CHARACTER VARYING(50),
+    "PULL_REQUEST_BINARY" BINARY LARGE OBJECT,
+    "MANUAL_BASELINE_ANALYSIS_UUID" CHARACTER VARYING(40),
+    "CREATED_AT" BIGINT NOT NULL,
+    "UPDATED_AT" BIGINT NOT NULL,
+    "EXCLUDE_FROM_PURGE" BOOLEAN DEFAULT FALSE NOT NULL,
+    "NEED_ISSUE_SYNC" BOOLEAN NOT NULL
+);
+ALTER TABLE "PROJECT_BRANCHES" ADD CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "UNIQ_PROJECT_BRANCHES" ON "PROJECT_BRANCHES"("BRANCH_TYPE" NULLS FIRST, "PROJECT_UUID" NULLS FIRST, "KEE" NULLS FIRST);
index a34d53c3dcdbde57a953873697d305c72f5b82cd..e9b72c81f61cc11a9ac53353fbf80276ba9a8b32 100644 (file)
@@ -298,7 +298,7 @@ public class ActivityAction implements CeWsAction {
       .setNameOrKeyQuery(componentQuery)
       .setQualifiers(POSSIBLE_QUALIFIERS)
       .build();
-    List<ComponentDto> componentDtos = dbClient.componentDao().selectByQuery(dbSession, componentDtoQuery, 0, CeTaskQuery.MAX_COMPONENT_UUIDS);
+    List<ComponentDto> componentDtos = dbClient.componentDao().selectByQuery(dbSession, componentDtoQuery, forPage(1).andSize(CeTaskQuery.MAX_COMPONENT_UUIDS));
     return dbClient.entityDao().selectByKeys(dbSession, componentDtos.stream().map(ComponentDto::getKey).collect(toSet()));
   }
 
index 62df55b54b72cb096a03e813e5c3fe9c98b5b64b..f9b67c3b063e3a032b462eb94eb0486b5e1d2c9e 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.core.i18n.I18n;
 import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.ce.CeTaskQuery;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentQuery;
 import org.sonar.db.entity.EntityDto;
@@ -54,6 +55,7 @@ import static java.util.Collections.singleton;
 import static java.util.Objects.requireNonNull;
 import static java.util.Optional.ofNullable;
 import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
+import static org.sonar.db.Pagination.forPage;
 import static org.sonar.server.permission.PermissionPrivilegeChecker.checkGlobalAdmin;
 import static org.sonar.server.permission.ws.template.WsTemplateRef.newTemplateRef;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
@@ -156,7 +158,7 @@ public class BulkApplyTemplateAction implements PermissionsWsAction {
       checkGlobalAdmin(userSession);
 
       ComponentQuery componentQuery = buildDbQuery(request);
-      List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, componentQuery, 0, Integer.MAX_VALUE);
+      List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, componentQuery, forPage(1).andSize(CeTaskQuery.MAX_COMPONENT_UUIDS));
 
       Set<String> entityUuids = components.stream()
         .map(ComponentDto::getKey)
index 1e2e947e031861b6867088eb3ac249c94055e729..85e324f47ab6c16b79d11d258874e298d73a2fa9 100644 (file)
@@ -57,6 +57,7 @@ import static java.util.stream.Collectors.toSet;
 import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.resources.Qualifiers.VIEW;
+import static org.sonar.db.Pagination.forPage;
 import static org.sonar.server.project.ws.SearchAction.buildDbQuery;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_002;
@@ -150,7 +151,7 @@ public class BulkDeleteAction implements ProjectsWsAction {
       checkIfAnalyzedBeforeIsFutureDate(searchRequest);
 
       ComponentQuery query = buildDbQuery(searchRequest);
-      Set<ComponentDto> componentDtos = new HashSet<>(dbClient.componentDao().selectByQuery(dbSession, query, 0, Integer.MAX_VALUE));
+      Set<ComponentDto> componentDtos = new HashSet<>(dbClient.componentDao().selectByQuery(dbSession, query, forPage(1).andSize(Integer.MAX_VALUE)));
       List<EntityDto> entities = dbClient.entityDao().selectByKeys(dbSession, componentDtos.stream().map(ComponentDto::getKey).collect(toSet()));
       Set<String> entityUuids = entities.stream().map(EntityDto::getUuid).collect(toSet());
       Map<String, String> mainBranchUuidByEntityUuid = dbClient.branchDao().selectMainBranchesByProjectUuids(dbSession, entityUuids).stream()
index 5dae2765f75ad02651d4af8fd921e1753bdd7f39..454617944fd70d23caefb5e1d59a416f38de7ff0 100644 (file)
@@ -53,6 +53,7 @@ import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.resources.Qualifiers.VIEW;
 import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.api.utils.DateUtils.parseDateOrDateTime;
+import static org.sonar.db.Pagination.forPage;
 import static org.sonar.server.project.Visibility.PRIVATE;
 import static org.sonar.server.project.Visibility.PUBLIC;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
@@ -167,7 +168,7 @@ public class SearchAction implements ProjectsWsAction {
 
       ComponentQuery query = buildDbQuery(request);
       Paging paging = buildPaging(dbSession, request, query);
-      List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, query, paging.offset(), paging.pageSize());
+      List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, query, forPage(paging.pageIndex()).andSize(paging.pageSize()));
       Set<String> componentUuids = components.stream().map(ComponentDto::uuid).collect(Collectors.toSet());
       List<BranchDto> branchDtos = dbClient.branchDao().selectByUuids(dbSession, componentUuids);
       Map<String, String> componentUuidToProjectUuid = branchDtos.stream().collect(Collectors.toMap(BranchDto::getUuid,BranchDto::getProjectUuid));