]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8325 Improve MeasureDao#selectByQuery performance
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 2 Nov 2016 13:38:31 +0000 (14:38 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 3 Nov 2016 14:36:47 +0000 (15:36 +0100)
It now accepts either a list of projects or a project with a list of components, but no more a list of components of any projects

It's no more possible to query a list of any components, because when doing that the performance are very bad (it would probably requires to add a column PROJECT_MEASURES.PROJECT_UUID)

server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchMyProjectsDataLoader.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/ProjectStatusAction.java
sonar-db/src/main/java/org/sonar/db/measure/MeasureDao.java
sonar-db/src/main/java/org/sonar/db/measure/MeasureQuery.java
sonar-db/src/main/resources/org/sonar/db/measure/MeasureMapper.xml
sonar-db/src/test/java/org/sonar/db/measure/MeasureDaoTest.java
sonar-db/src/test/java/org/sonar/db/measure/MeasureQueryTest.java [new file with mode: 0644]

index 6304bc21367c0fc03727ae59f36c91889dd5744e..8a2d059c215e3c288c3de22775e53edad2f409a6 100644 (file)
@@ -209,8 +209,8 @@ public class ComponentTreeDataLoader {
     Map<Integer, MetricDto> metricsById = Maps.uniqueIndex(metrics, MetricDtoFunctions.toId());
     MeasureQuery measureQuery = MeasureQuery.builder()
       .setPersonId(developerId)
-      .setComponentUuids(componentUuids)
-      .setMetricIds(metricsById.keySet())
+      .setComponentUuids(baseComponent.projectUuid(), componentUuids)
+      .setMetricIds(new ArrayList<>(metricsById.keySet()))
       .build();
     List<MeasureDto> measureDtos = dbClient.measureDao().selectByQuery(dbSession, measureQuery);
 
index 61d6e70e5c86dd752543e4e65a4631c7e6d0aacd..2fc599297f079e37d3f8298e7c8785296be1fa2c 100644 (file)
@@ -153,7 +153,7 @@ public class SearchAction implements MeasuresWsAction {
 
     private List<MeasureDto> searchMeasures() {
       return dbClient.measureDao().selectByQuery(dbSession, MeasureQuery.builder()
-        .setComponentUuids(projects.stream().map(ComponentDto::uuid).collect(toList()))
+        .setProjectUuids(projects.stream().map(ComponentDto::uuid).collect(toList()))
         .setMetricIds(metrics.stream().map(MetricDto::getId).collect(toList()))
         .build());
     }
index 879c9b3099dca8306dc793f1e36cd10d115837b7..648faeeb49908527cac7c4afadf0891ac593c1b8 100644 (file)
@@ -62,7 +62,7 @@ public class SearchMyProjectsDataLoader {
       List<SnapshotDto> snapshots = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids);
       MetricDto gateStatusMetric = dbClient.metricDao().selectOrFailByKey(dbSession, CoreMetrics.ALERT_STATUS_KEY);
       MeasureQuery measureQuery = MeasureQuery.builder()
-        .setComponentUuids(projectUuids)
+        .setProjectUuids(projectUuids)
         .setMetricId(gateStatusMetric.getId())
         .build();
       List<MeasureDto> qualityGates = dbClient.measureDao().selectByQuery(dbSession, measureQuery);
index 704474df243a16d478f740b912b7685d7cc44fa5..fae8f46b7d41c52cf236f45d3523c272b67b256d 100644 (file)
@@ -48,6 +48,7 @@ import org.sonarqube.ws.WsQualityGates.ProjectStatusWsResponse;
 import org.sonarqube.ws.client.qualitygate.ProjectStatusWsRequest;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.Collections.singletonList;
 import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
 import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 import static org.sonar.server.ws.WsUtils.checkRequest;
@@ -162,7 +163,7 @@ public class ProjectStatusAction implements QualityGatesWsAction {
 
   private Optional<String> getQualityGateDetailsMeasureData(DbSession dbSession, ComponentDto project) {
     MeasureQuery measureQuery = MeasureQuery.builder()
-      .setComponentUuid(project.projectUuid())
+      .setProjectUuids(singletonList(project.projectUuid()))
       .setMetricKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY)
       .build();
     List<MeasureDto> measures = dbClient.measureDao().selectByQuery(dbSession, measureQuery);
index 0536a8389d6a57307fde7ddf952210892f9a185d..c298bb18ff1cf7bccbc3be499c848eae54e3d9f9 100644 (file)
@@ -41,8 +41,11 @@ public class MeasureDao implements Dao {
 
   /**
    * Selects the measures of either the last analysis (when {@link MeasureQuery#analysisUuid} is {@code null}) or of the
-   * specified analysis (given by {@link MeasureQuery#analysisUuid}) for the component UUIDs specified in
-   * {@link MeasureQuery#componentUuids}.
+   * specified analysis (given by {@link MeasureQuery#analysisUuid}).
+   * The components can be specified either as :
+   * - A list of projects in {@link MeasureQuery#projectUuids}
+   * - A list of components in {@link MeasureQuery#componentUuids} with one mandatory project in {@link MeasureQuery#projectUuids}
+   * - One single component in  {@link MeasureQuery#componentUuids}
    * <p>
    * In addition, this method returns measures which are not associated to any developer, unless one is specified in
    * {@link MeasureQuery#personId}.
@@ -56,22 +59,29 @@ public class MeasureDao implements Dao {
     if (query.returnsEmpty()) {
       return Collections.emptyList();
     }
-    if (query.getComponentUuids() == null) {
-      return mapper(dbSession).selectByQuery(query);
+    if (query.getComponentUuids() != null) {
+      return executeLargeInputs(
+        query.getComponentUuids(),
+        componentUuids -> {
+          MeasureQuery pageQuery = MeasureQuery.copyWithSubsetOfComponentUuids(query, componentUuids);
+          return mapper(dbSession).selectByQuery(pageQuery);
+        });
+    } else if (query.getProjectUuids() != null) {
+      return executeLargeInputs(
+        query.getProjectUuids(),
+        projectUuids -> {
+          MeasureQuery pageQuery = MeasureQuery.copyWithSubsetOfProjectUuids(query, projectUuids);
+          return mapper(dbSession).selectByQuery(pageQuery);
+        });
     }
-    return executeLargeInputs(query.getComponentUuids(), componentUuids -> {
-      MeasureQuery pageQuery = MeasureQuery.copyWithSubsetOfComponentUuids(query, componentUuids);
-      return mapper(dbSession).selectByQuery(pageQuery);
-    });
+    return mapper(dbSession).selectByQuery(query);
   }
 
   public void selectByQuery(DbSession dbSession, MeasureQuery query, ResultHandler resultHandler) {
     if (query.returnsEmpty()) {
       return;
     }
-    if (query.getComponentUuids() == null) {
-      mapper(dbSession).selectByQuery(query, resultHandler);
-    } else {
+    if (query.getComponentUuids() != null) {
       executeLargeInputsWithoutOutput(
         query.getComponentUuids(),
         componentUuids -> {
@@ -79,7 +89,16 @@ public class MeasureDao implements Dao {
           mapper(dbSession).selectByQuery(pageQuery, resultHandler);
           return null;
         });
+    } else if (query.getProjectUuids() != null) {
+      executeLargeInputsWithoutOutput(
+        query.getProjectUuids(),
+        projectUuids -> {
+          MeasureQuery pageQuery = MeasureQuery.copyWithSubsetOfProjectUuids(query, projectUuids);
+          mapper(dbSession).selectByQuery(pageQuery, resultHandler);
+          return null;
+        });
     }
+    mapper(dbSession).selectByQuery(query, resultHandler);
   }
 
   public List<PastMeasureDto> selectPastMeasures(DbSession dbSession,
index 80b49427fccb99a65233c272fb22ceeefbc89703..00b724df17555645e06c2be66bd6e5c3205939ab 100644 (file)
  */
 package org.sonar.db.measure;
 
-import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.Collections.singleton;
+import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Collections.singletonList;
 import static java.util.Objects.requireNonNull;
 
 public class MeasureQuery {
   private final String analysisUuid;
+
+  @CheckForNull
+  private final List<String> projectUuids;
+
+  @CheckForNull
   private final List<String> componentUuids;
+
   @CheckForNull
-  private final Collection<Integer> metricIds;
+  private final List<Integer> metricIds;
+
   @CheckForNull
-  private final Collection<String> metricKeys;
+  private final List<String> metricKeys;
+
   @CheckForNull
   private final Long personId;
 
   private MeasureQuery(Builder builder) {
-    this(builder.analysisUuid, builder.componentUuids, builder.metricIds, builder.metricKeys, builder.personId);
+    this(builder.analysisUuid, builder.projectUuids, builder.componentUuids, builder.metricIds, builder.metricKeys, builder.personId);
   }
 
   private MeasureQuery(@Nullable String analysisUuid,
-    List<String> componentUuids,
-    @Nullable Collection<Integer> metricIds,
-    @Nullable Collection<String> metricKeys,
+    @Nullable List<String> projectUuids,
+    @Nullable List<String> componentUuids,
+    @Nullable List<Integer> metricIds,
+    @Nullable List<String> metricKeys,
     @Nullable Long personId) {
-    requireNonNull(componentUuids, "Component UUIDs must be set");
-    checkState(metricIds == null || metricKeys == null, "Metric IDs and keys must not be set both");
+    checkArgument(metricIds == null || metricKeys == null, "Metric IDs and keys must not be set both");
+    checkArgument(projectUuids != null || componentUuids != null, "At least one filter on component UUID is expected");
+    checkArgument(componentUuids == null || componentUuids.size() == 1 || (projectUuids != null && projectUuids.size() == 1),
+      "Component UUIDs can only be used when a single project UUID is set");
+
     this.analysisUuid = analysisUuid;
+    this.projectUuids = projectUuids;
     this.componentUuids = componentUuids;
     this.metricIds = metricIds;
     this.metricKeys = metricKeys;
@@ -62,17 +73,33 @@ public class MeasureQuery {
     return analysisUuid;
   }
 
+  @CheckForNull
+  public List<String> getProjectUuids() {
+    return projectUuids;
+  }
+
+  @CheckForNull
+  public String getProjectUuid() {
+    return isOnComponents() ? projectUuids.get(0) : null;
+  }
+
+  @CheckForNull
   public List<String> getComponentUuids() {
     return componentUuids;
   }
 
   @CheckForNull
-  public Collection<Integer> getMetricIds() {
+  public String getComponentUuid() {
+    return isOnSingleComponent() ? componentUuids.get(0) : null;
+  }
+
+  @CheckForNull
+  public List<Integer> getMetricIds() {
     return metricIds;
   }
 
   @CheckForNull
-  public Collection<String> getMetricKeys() {
+  public List<String> getMetricKeys() {
     return metricKeys;
   }
 
@@ -82,11 +109,24 @@ public class MeasureQuery {
   }
 
   public boolean returnsEmpty() {
-    return componentUuids.isEmpty()
+    return (projectUuids != null && projectUuids.isEmpty())
+      || (componentUuids != null && componentUuids.isEmpty())
       || (metricIds != null && metricIds.isEmpty())
       || (metricKeys != null && metricKeys.isEmpty());
   }
 
+  public boolean isOnProjects() {
+    return projectUuids != null && componentUuids == null;
+  }
+
+  public boolean isOnComponents() {
+    return projectUuids != null && projectUuids.size() == 1 && componentUuids != null;
+  }
+
+  public boolean isOnSingleComponent() {
+    return projectUuids == null && componentUuids != null && componentUuids.size() == 1;
+  }
+
   @Override
   public boolean equals(@Nullable Object o) {
     if (this == o) {
@@ -97,10 +137,11 @@ public class MeasureQuery {
     }
     MeasureQuery that = (MeasureQuery) o;
     return Objects.equals(analysisUuid, that.analysisUuid) &&
-        Objects.equals(componentUuids, that.componentUuids) &&
-        Objects.equals(metricIds, that.metricIds) &&
-        Objects.equals(metricKeys, that.metricKeys) &&
-        Objects.equals(personId, that.personId);
+      Objects.equals(projectUuids, that.projectUuids) &&
+      Objects.equals(componentUuids, that.componentUuids) &&
+      Objects.equals(metricIds, that.metricIds) &&
+      Objects.equals(metricKeys, that.metricKeys) &&
+      Objects.equals(personId, that.personId);
   }
 
   @Override
@@ -112,15 +153,20 @@ public class MeasureQuery {
     return new Builder();
   }
 
+  static MeasureQuery copyWithSubsetOfProjectUuids(MeasureQuery query, List<String> projectUuids) {
+    return new MeasureQuery(query.analysisUuid, projectUuids, query.componentUuids, query.metricIds, query.metricKeys, query.personId);
+  }
+
   static MeasureQuery copyWithSubsetOfComponentUuids(MeasureQuery query, List<String> componentUuids) {
-    return new MeasureQuery(query.analysisUuid, componentUuids, query.metricIds, query.metricKeys, query.personId);
+    return new MeasureQuery(query.analysisUuid, query.projectUuids, componentUuids, query.metricIds, query.metricKeys, query.personId);
   }
 
   public static final class Builder {
     private String analysisUuid;
+    private List<String> projectUuids;
     private List<String> componentUuids;
-    private Collection<Integer> metricIds;
-    private Collection<String> metricKeys;
+    private List<Integer> metricIds;
+    private List<String> metricKeys;
     private Long personId;
 
     private Builder() {
@@ -132,11 +178,26 @@ public class MeasureQuery {
       return this;
     }
 
-    public Builder setComponentUuids(List<String> componentUuids) {
+    /**
+     * List of projects
+     */
+    public Builder setProjectUuids(@Nullable List<String> projectUuids) {
+      this.projectUuids = projectUuids;
+      return this;
+    }
+
+    /**
+     * List of components of a project
+     */
+    public Builder setComponentUuids(String projectUuid, List<String> componentUuids) {
+      setProjectUuids(singletonList(requireNonNull(projectUuid)));
       this.componentUuids = componentUuids;
       return this;
     }
 
+    /**
+     * Single component
+     */
     public Builder setComponentUuid(String componentUuid) {
       this.componentUuids = singletonList(componentUuid);
       return this;
@@ -145,26 +206,26 @@ public class MeasureQuery {
     /**
      * All the measures are returned if parameter is {@code null}.
      */
-    public Builder setMetricIds(@Nullable Collection<Integer> metricIds) {
+    public Builder setMetricIds(@Nullable List<Integer> metricIds) {
       this.metricIds = metricIds;
       return this;
     }
 
     public Builder setMetricId(int metricId) {
-      this.metricIds = singleton(metricId);
+      this.metricIds = singletonList(metricId);
       return this;
     }
 
     /**
      * All the measures are returned if parameter is {@code null}.
      */
-    public Builder setMetricKeys(@Nullable Collection<String> s) {
+    public Builder setMetricKeys(@Nullable List<String> s) {
       this.metricKeys = s;
       return this;
     }
 
     public Builder setMetricKey(String s) {
-      this.metricKeys = singleton(s);
+      this.metricKeys = singletonList(s);
       return this;
     }
 
index ab9bbcc31c522c31d782f17a259e907d09800817..0ed2c37a4ed0e73eaf3bb55d03d98dbe1fa24639 100644 (file)
     select
     <include refid="measureColumns"/>
     from
-      project_measures pm
-      inner join projects p on p.uuid=pm.component_uuid
-      inner join snapshots analysis on analysis.component_uuid = p.project_uuid and analysis.uuid = pm.analysis_uuid
-      <if test="query.getMetricKeys() != null">
-        inner join metrics m on m.id = pm.metric_id
-      </if>
+    project_measures pm
+    inner join snapshots analysis on analysis.uuid = pm.analysis_uuid
+    <if test="query.isOnComponents()">
+      inner join projects p on p.project_uuid=analysis.component_uuid and p.uuid=pm.component_uuid
+        and p.project_uuid=#{query.projectUuid}
+        and p.uuid in
+        <foreach item="componentUuid" collection="query.getComponentUuids()" open="(" separator="," close=")">
+          #{componentUuid}
+        </foreach>
+    </if>
+    <if test="query.isOnSingleComponent()">
+      inner join projects p on p.project_uuid=analysis.component_uuid and p.uuid=pm.component_uuid
+        and p.uuid=#{query.componentUuid}
+    </if>
+    <if test="query.getMetricKeys() != null">
+      inner join metrics m on m.id = pm.metric_id
+    </if>
     where
-      <if test="query.getAnalysisUuid() == null">
-        analysis.islast=${_true}
-      </if>
-      <if test="query.getAnalysisUuid() != null">
-        analysis.uuid = #{query.analysisUuid}
-      </if>
-      and p.uuid in
-      <foreach item="componentUuid" collection="query.getComponentUuids()" open="(" separator="," close=")">
-        #{componentUuid}
+    <if test="query.getAnalysisUuid() == null">
+      analysis.islast=${_true}
+    </if>
+    <if test="query.getAnalysisUuid() != null">
+      analysis.uuid = #{query.analysisUuid}
+    </if>
+    <if test="query.isOnProjects()">
+      and analysis.component_uuid=pm.component_uuid
+      and analysis.component_uuid in
+      <foreach item="projectUuid" collection="query.getProjectUuids()" open="(" separator="," close=")">
+        #{projectUuid}
       </foreach>
-      <if test="query.getMetricIds() != null">
-        and pm.metric_id in
-        <foreach item="metricId" collection="query.getMetricIds()" open="(" separator="," close=")">#{metricId}</foreach>
-      </if>
-      <if test="query.getMetricKeys() != null">
-        and m.name in
-        <foreach item="metricKey" collection="query.getMetricKeys()" open="(" separator="," close=")">
-          #{metricKey}
-        </foreach>
-      </if>
-      <choose>
-        <when test="query.getPersonId() != null">
-          and person_id = #{query.personId}
-        </when>
-        <otherwise>
-          and person_id is null
-        </otherwise>
-      </choose>
+    </if>
+    <if test="query.getMetricIds() != null">
+      and pm.metric_id in
+      <foreach item="metricId" collection="query.getMetricIds()" open="(" separator="," close=")">#{metricId}</foreach>
+    </if>
+    <if test="query.getMetricKeys() != null">
+      and m.name in
+      <foreach item="metricKey" collection="query.getMetricKeys()" open="(" separator="," close=")">
+        #{metricKey}
+      </foreach>
+    </if>
+    <choose>
+      <when test="query.getPersonId() != null">
+        and person_id = #{query.personId}
+      </when>
+      <otherwise>
+        and person_id is null
+      </otherwise>
+    </choose>
   </select>
 
   <select id="selectPastMeasures" parameterType="map" resultType="org.sonar.db.measure.PastMeasureDto">
index d60413394e5bdfe90fa449b14143954eac8eeb44..af492d3666fd1130f674c0c31c5ca44bebbcc5a4 100644 (file)
@@ -105,12 +105,24 @@ public class MeasureDaoTest {
 
   @Test
   public void selectByQuery() {
-    ComponentDto project = db.components().insertProject();
-    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
+    ComponentDto project1 = db.components().insertProject();
+    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project1));
     db.components().insertComponent(newFileDto(module).setUuid("C1"));
     db.components().insertComponent(newFileDto(module).setUuid("C2"));
-    insertAnalysis(LAST_ANALYSIS_UUID, project.uuid(), true);
-    insertAnalysis(OTHER_ANALYSIS_UUID, project.uuid(), false);
+    insertAnalysis(LAST_ANALYSIS_UUID, project1.uuid(), true);
+    insertAnalysis(OTHER_ANALYSIS_UUID, project1.uuid(), false);
+
+    String project2LastAnalysisUuid = "P2_LAST_ANALYSIS";
+    ComponentDto project2 = db.components().insertProject();
+    insertAnalysis(project2LastAnalysisUuid, project2.uuid(), true);
+
+    // project 1
+    insertMeasure("P1_M1", LAST_ANALYSIS_UUID, project1.uuid(), NCLOC_METRIC_ID);
+    insertMeasure("P1_M1", LAST_ANALYSIS_UUID, project1.uuid(), COVERAGE_METRIC_ID);
+    insertMeasure("P1_M3", OTHER_ANALYSIS_UUID, project1.uuid(), NCLOC_METRIC_ID);
+    // project 2
+    insertMeasure("P2_M1", project2LastAnalysisUuid, project2.uuid(), NCLOC_METRIC_ID);
+    insertMeasure("P2_M2", project2LastAnalysisUuid, project2.uuid(), COVERAGE_METRIC_ID);
     // component C1
     insertMeasure("M1", OTHER_ANALYSIS_UUID, "C1", NCLOC_METRIC_ID);
     insertMeasure("M2", LAST_ANALYSIS_UUID, "C1", NCLOC_METRIC_ID);
@@ -121,8 +133,10 @@ public class MeasureDaoTest {
     insertMeasure("M6", LAST_ANALYSIS_UUID, "C2", NCLOC_METRIC_ID);
     db.commit();
 
-    verifyZeroMeasures(MeasureQuery.builder().setComponentUuids(emptyList()));
+    verifyZeroMeasures(MeasureQuery.builder().setComponentUuids(project1.uuid(), emptyList()));
     verifyZeroMeasures(MeasureQuery.builder().setComponentUuid("MISSING_COMPONENT"));
+    verifyZeroMeasures(MeasureQuery.builder().setProjectUuids(emptyList()));
+    verifyZeroMeasures(MeasureQuery.builder().setProjectUuids(singletonList("MISSING_COMPONENT")));
 
     // all measures of component C1 of last analysis
     verifyMeasures(MeasureQuery.builder().setComponentUuid("C1"), "M2", "M3");
@@ -153,11 +167,11 @@ public class MeasureDaoTest {
     verifyZeroMeasures(MeasureQuery.builder().setComponentUuid("C1").setAnalysisUuid(LAST_ANALYSIS_UUID).setMetricId(COMPLEXITY_METRIC_ID));
 
     // ncloc measures of components C1, C2 and C3 (which does not exist) of last analysis
-    verifyMeasures(MeasureQuery.builder().setComponentUuids(asList("C1", "C2", "C3")), "M2", "M3", "M6");
+    verifyMeasures(MeasureQuery.builder().setComponentUuids(project1.uuid(), asList("C1", "C2", "C3")), "M2", "M3", "M6");
     // ncloc measures of components C1, C2 and C3 (which does not exist) of non last analysis
-    verifyMeasures(MeasureQuery.builder().setComponentUuids(asList("C1", "C2", "C3")).setAnalysisUuid(OTHER_ANALYSIS_UUID), "M1");
+    verifyMeasures(MeasureQuery.builder().setComponentUuids(project1.uuid(), asList("C1", "C2", "C3")).setAnalysisUuid(OTHER_ANALYSIS_UUID), "M1");
     // ncloc measures of components C1, C2 and C3 (which does not exist) of last analysis by UUID
-    verifyMeasures(MeasureQuery.builder().setComponentUuids(asList("C1", "C2", "C3")).setAnalysisUuid(LAST_ANALYSIS_UUID), "M2", "M3", "M6");
+    verifyMeasures(MeasureQuery.builder().setComponentUuids(project1.uuid(), asList("C1", "C2", "C3")).setAnalysisUuid(LAST_ANALYSIS_UUID), "M2", "M3", "M6");
 
     // measures of missing developer of component C1 of last analysis
     verifyZeroMeasures(MeasureQuery.builder().setComponentUuid("C1").setPersonId(123L));
@@ -172,15 +186,38 @@ public class MeasureDaoTest {
     verifyZeroMeasures(MeasureQuery.builder().setComponentUuid("C1").setAnalysisUuid(OTHER_ANALYSIS_UUID).setPersonId(A_PERSON_ID));
     // developer measures of component C1 of last analysis by UUID
     verifyMeasures(MeasureQuery.builder().setComponentUuid("C1").setAnalysisUuid(LAST_ANALYSIS_UUID).setPersonId(A_PERSON_ID), "M4");
+
+    // projects measures of last analysis
+    verifyMeasures(MeasureQuery.builder().setProjectUuids(singletonList(project1.uuid())).setMetricId(NCLOC_METRIC_ID), "P1_M1");
+    verifyMeasures(MeasureQuery.builder().setProjectUuids(asList(project1.uuid(), project2.uuid())).setMetricIds(asList(NCLOC_METRIC_ID, COVERAGE_METRIC_ID)),
+      "P1_M1", "P2_M1", "P2_M2", "P2_M2");
+    verifyMeasures(MeasureQuery.builder().setProjectUuids(asList(project1.uuid(), project2.uuid(), "UNKNOWN")).setMetricId(NCLOC_METRIC_ID), "P1_M1", "P2_M1");
+
+    // projects measures of none last analysis
+    verifyMeasures(MeasureQuery.builder().setProjectUuids(singletonList(project1.uuid())).setMetricId(NCLOC_METRIC_ID).setAnalysisUuid(OTHER_ANALYSIS_UUID), "P1_M3");
+    verifyMeasures(MeasureQuery.builder().setProjectUuids(asList(project1.uuid(), project2.uuid())).setMetricId(NCLOC_METRIC_ID).setAnalysisUuid(OTHER_ANALYSIS_UUID), "P1_M3");
   }
 
   @Test
   public void selectByQuery_with_handler() {
-    ComponentDto project = db.components().insertProject();
-    db.components().insertComponent(newFileDto(project).setUuid("C1"));
-    db.components().insertComponent(newFileDto(project).setUuid("C2"));
-    insertAnalysis(LAST_ANALYSIS_UUID, project.uuid(), true);
-    insertAnalysis(OTHER_ANALYSIS_UUID, project.uuid(), false);
+    ComponentDto project1 = db.components().insertProject();
+    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project1));
+    db.components().insertComponent(newFileDto(module).setUuid("C1"));
+    db.components().insertComponent(newFileDto(module).setUuid("C2"));
+    insertAnalysis(LAST_ANALYSIS_UUID, project1.uuid(), true);
+    insertAnalysis(OTHER_ANALYSIS_UUID, project1.uuid(), false);
+
+    String project2LastAnalysisUuid = "P2_LAST_ANALYSIS";
+    ComponentDto project2 = db.components().insertProject();
+    insertAnalysis(project2LastAnalysisUuid, project2.uuid(), true);
+
+    // project 1
+    insertMeasure("P1_M1", LAST_ANALYSIS_UUID, project1.uuid(), NCLOC_METRIC_ID);
+    insertMeasure("P1_M1", LAST_ANALYSIS_UUID, project1.uuid(), COVERAGE_METRIC_ID);
+    insertMeasure("P1_M3", OTHER_ANALYSIS_UUID, project1.uuid(), NCLOC_METRIC_ID);
+    // project 2
+    insertMeasure("P2_M1", project2LastAnalysisUuid, project2.uuid(), NCLOC_METRIC_ID);
+    insertMeasure("P2_M2", project2LastAnalysisUuid, project2.uuid(), COVERAGE_METRIC_ID);
     // component C1
     insertMeasure("M1", OTHER_ANALYSIS_UUID, "C1", NCLOC_METRIC_ID);
     insertMeasure("M2", LAST_ANALYSIS_UUID, "C1", NCLOC_METRIC_ID);
@@ -191,8 +228,10 @@ public class MeasureDaoTest {
     insertMeasure("M6", LAST_ANALYSIS_UUID, "C2", NCLOC_METRIC_ID);
     db.commit();
 
-    verifyZeroMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(emptyList()));
+    verifyZeroMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(project1.uuid(), emptyList()));
     verifyZeroMeasuresWithHandler(MeasureQuery.builder().setComponentUuid("MISSING_COMPONENT"));
+    verifyZeroMeasuresWithHandler(MeasureQuery.builder().setProjectUuids(emptyList()));
+    verifyZeroMeasuresWithHandler(MeasureQuery.builder().setProjectUuids(singletonList("MISSING_COMPONENT")));
 
     // all measures of component C1 of last analysis
     verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuid("C1"), "M2", "M3");
@@ -224,11 +263,11 @@ public class MeasureDaoTest {
     verifyZeroMeasuresWithHandler(MeasureQuery.builder().setComponentUuid("C1").setAnalysisUuid(LAST_ANALYSIS_UUID).setMetricId(COMPLEXITY_METRIC_ID));
 
     // ncloc measures of components C1, C2 and C3 (which does not exist) of last analysis
-    verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(asList("C1", "C2", "C3")), "M2", "M3", "M6");
+    verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(project1.uuid(), asList("C1", "C2", "C3")), "M2", "M3", "M6");
     // ncloc measures of components C1, C2 and C3 (which does not exist) of non last analysis
-    verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(asList("C1", "C2", "C3")).setAnalysisUuid(OTHER_ANALYSIS_UUID), "M1");
+    verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(project1.uuid(), asList("C1", "C2", "C3")).setAnalysisUuid(OTHER_ANALYSIS_UUID), "M1");
     // ncloc measures of components C1, C2 and C3 (which does not exist) of last analysis by UUID
-    verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(asList("C1", "C2", "C3")).setAnalysisUuid(LAST_ANALYSIS_UUID), "M2", "M3", "M6");
+    verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuids(project1.uuid(), asList("C1", "C2", "C3")).setAnalysisUuid(LAST_ANALYSIS_UUID), "M2", "M3", "M6");
 
     // measures of missing developer of component C1 of last analysis
     verifyZeroMeasuresWithHandler(MeasureQuery.builder().setComponentUuid("C1").setPersonId(123L));
@@ -243,6 +282,17 @@ public class MeasureDaoTest {
     verifyZeroMeasuresWithHandler(MeasureQuery.builder().setComponentUuid("C1").setAnalysisUuid(OTHER_ANALYSIS_UUID).setPersonId(A_PERSON_ID));
     // developer measures of component C1 of last analysis by UUID
     verifyMeasuresWithHandler(MeasureQuery.builder().setComponentUuid("C1").setAnalysisUuid(LAST_ANALYSIS_UUID).setPersonId(A_PERSON_ID), "M4");
+
+    // projects measures of last analysis
+    verifyMeasuresWithHandler(MeasureQuery.builder().setProjectUuids(singletonList(project1.uuid())).setMetricId(NCLOC_METRIC_ID), "P1_M1");
+    verifyMeasuresWithHandler(MeasureQuery.builder().setProjectUuids(asList(project1.uuid(), project2.uuid())).setMetricIds(asList(NCLOC_METRIC_ID, COVERAGE_METRIC_ID)),
+      "P1_M1", "P2_M1", "P2_M2", "P2_M2");
+    verifyMeasuresWithHandler(MeasureQuery.builder().setProjectUuids(asList(project1.uuid(), project2.uuid(), "UNKNOWN")).setMetricId(NCLOC_METRIC_ID), "P1_M1", "P2_M1");
+
+    // projects measures of none last analysis
+    verifyMeasuresWithHandler(MeasureQuery.builder().setProjectUuids(singletonList(project1.uuid())).setMetricId(NCLOC_METRIC_ID).setAnalysisUuid(OTHER_ANALYSIS_UUID), "P1_M3");
+    verifyMeasuresWithHandler(MeasureQuery.builder().setProjectUuids(asList(project1.uuid(), project2.uuid())).setMetricId(NCLOC_METRIC_ID).setAnalysisUuid(OTHER_ANALYSIS_UUID),
+      "P1_M3");
   }
 
   @Test
@@ -254,7 +304,7 @@ public class MeasureDaoTest {
     insertMeasure("M2", LAST_ANALYSIS_UUID, "C1", COMPLEXITY_METRIC_ID);
     db.commit();
 
-    assertThat(selectSingle(MeasureQuery.builder().setComponentUuids(emptyList()))).isNotPresent();
+    assertThat(selectSingle(MeasureQuery.builder().setComponentUuids(project.uuid(), emptyList()))).isNotPresent();
     assertThat(selectSingle(MeasureQuery.builder().setComponentUuid("MISSING_COMPONENT"))).isNotPresent();
 
     // select a single measure
diff --git a/sonar-db/src/test/java/org/sonar/db/measure/MeasureQueryTest.java b/sonar-db/src/test/java/org/sonar/db/measure/MeasureQueryTest.java
new file mode 100644 (file)
index 0000000..e8de3c2
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.db.measure;
+
+import java.util.Collections;
+import org.assertj.core.api.Java6Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MeasureQueryTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void create_query_from_projects() throws Exception {
+    MeasureQuery query = MeasureQuery.builder().setProjectUuids(asList("PROJECT_1", "PROJECT_2")).build();
+
+    assertThat(query.getProjectUuids()).containsOnly("PROJECT_1", "PROJECT_2");
+    assertThat(query.isOnProjects()).isTrue();
+    assertThat(query.isOnComponents()).isFalse();
+    assertThat(query.isOnSingleComponent()).isFalse();
+  }
+
+  @Test
+  public void create_query_from_project_and_components() throws Exception {
+    MeasureQuery query = MeasureQuery.builder().setComponentUuids("PROJECT_1", asList("FILE_1", "FILE_2")).build();
+
+    assertThat(query.getProjectUuids()).containsOnly("PROJECT_1");
+    assertThat(query.getProjectUuid()).isEqualTo("PROJECT_1");
+    assertThat(query.getComponentUuids()).containsOnly("FILE_1", "FILE_2");
+    assertThat(query.isOnProjects()).isFalse();
+    assertThat(query.isOnComponents()).isTrue();
+    assertThat(query.isOnSingleComponent()).isFalse();
+  }
+
+  @Test
+  public void create_query_from_single_component_uuid() throws Exception {
+    MeasureQuery query = MeasureQuery.builder().setComponentUuid("FILE_1").build();
+
+    assertThat(query.getComponentUuids()).containsOnly("FILE_1");
+    assertThat(query.getComponentUuid()).isEqualTo("FILE_1");
+    assertThat(query.isOnProjects()).isFalse();
+    assertThat(query.isOnComponents()).isFalse();
+    assertThat(query.isOnSingleComponent()).isTrue();
+  }
+
+  @Test
+  public void create_query_from_metric_ids() throws Exception {
+    MeasureQuery query = MeasureQuery.builder().setProjectUuids(asList("PROJECT_1", "PROJECT_2")).setMetricIds(asList(10, 11)).build();
+
+    assertThat(query.getMetricIds()).containsOnly(10, 11);
+    assertThat(query.getMetricKeys()).isNull();
+  }
+
+  @Test
+  public void create_query_from_metric_keys() throws Exception {
+    MeasureQuery query = MeasureQuery.builder().setProjectUuids(asList("PROJECT_1", "PROJECT_2")).setMetricKeys(asList("M1", "M2")).build();
+
+    assertThat(query.getMetricKeys()).containsOnly("M1", "M2");
+    assertThat(query.getMetricIds()).isNull();
+  }
+
+  @Test
+  public void create_query_from_person_id() throws Exception {
+    MeasureQuery query = MeasureQuery.builder().setProjectUuids(asList("PROJECT_1", "PROJECT_2")).setPersonId(100L).build();
+
+    assertThat(query.getPersonId()).isEqualTo(100L);
+  }
+
+  @Test
+  public void return_empty_when_metrics_are_empty() throws Exception {
+    Java6Assertions.assertThat(MeasureQuery.builder()
+      .setProjectUuids(asList("PROJECT_1", "PROJECT_2"))
+      .setMetricKeys(Collections.emptyList())
+      .build().returnsEmpty()).isTrue();
+
+    Java6Assertions.assertThat(MeasureQuery.builder()
+      .setProjectUuids(asList("PROJECT_1", "PROJECT_2"))
+      .setMetricIds(Collections.emptyList())
+      .build().returnsEmpty()).isTrue();
+  }
+
+  @Test
+  public void return_empty_when_projects_are_empty() throws Exception {
+    Java6Assertions.assertThat(MeasureQuery.builder()
+      .setProjectUuids(Collections.emptyList())
+      .build().returnsEmpty()).isTrue();
+  }
+
+  @Test
+  public void return_empty_when_components_are_empty() throws Exception {
+    Java6Assertions.assertThat(MeasureQuery.builder()
+      .setComponentUuids("PROJECT", Collections.emptyList())
+      .build().returnsEmpty()).isTrue();
+  }
+
+  @Test
+  public void fail_when_no_component_uuid_filter() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At least one filter on component UUID is expected");
+    MeasureQuery.builder().build();
+  }
+
+  @Test
+  public void fail_when_component_uuids_without_project_uuid() throws Exception {
+    expectedException.expect(NullPointerException.class);
+    MeasureQuery.builder().setComponentUuids(null, asList("FILE_1", "FILE_2")).build();
+  }
+
+  @Test
+  public void fail_when_using_metric_ids_and_metric_keys() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Metric IDs and keys must not be set both");
+    MeasureQuery.builder().setMetricIds(asList(10, 11)).setMetricKeys(asList("M1", "M2")).setProjectUuids(asList("PROJECT_1", "PROJECT_2")).build();
+  }
+
+}