]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8221 Fix project measures indexing on MySQL
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 21 Oct 2016 11:00:00 +0000 (13:00 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 24 Oct 2016 10:02:33 +0000 (12:02 +0200)
Replace streaming of projects by first loading all projects once, then load measures project by project

server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresDoc.java
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndexer.java
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresResultSetIterator.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresResultSetIteratorTest.java [deleted file]
sonar-db/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java

index 67f0590af774bbecabc053d9bf677f175fb0b870..357c271938908f0ff8206c07e5531223bf706016 100644 (file)
@@ -94,10 +94,10 @@ public class ProjectMeasuresDoc extends BaseDoc {
     return this;
   }
 
-  public ProjectMeasuresDoc setMeasuresFromMap(Map<String, Object> measures) {
+  public ProjectMeasuresDoc setMeasuresFromMap(Map<String, Double> measures) {
     setMeasures(
       measures.entrySet().stream()
-        .map(entry -> ImmutableMap.of(
+        .map(entry -> ImmutableMap.<String, Object>of(
           ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY, entry.getKey(),
           ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE, entry.getValue()))
         .collect(Collectors.toList()));
index 59da24cedfd67f21628f36dcdc4d4b223a2bbb84..a7b32d9cccf7895151ac095346269a62f8d7d73b 100644 (file)
@@ -26,6 +26,8 @@ import javax.annotation.Nullable;
 import org.elasticsearch.action.index.IndexRequest;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.measure.ProjectMeasuresIndexerIterator;
+import org.sonar.db.measure.ProjectMeasuresIndexerIterator.ProjectMeasures;
 import org.sonar.server.es.BaseIndexer;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.EsClient;
@@ -67,24 +69,22 @@ public class ProjectMeasuresIndexer extends BaseIndexer {
   }
 
   private long doIndex(BulkIndexer bulk, long lastUpdatedAt, @Nullable String projectUuid) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      ProjectMeasuresResultSetIterator rowIt = ProjectMeasuresResultSetIterator.create(dbClient, dbSession, lastUpdatedAt, projectUuid);
-      long maxDate = doIndex(bulk, rowIt);
-      rowIt.close();
-      return maxDate;
+    try (DbSession dbSession = dbClient.openSession(false);
+      ProjectMeasuresIndexerIterator rowIt = ProjectMeasuresIndexerIterator.create(dbSession, lastUpdatedAt, projectUuid)) {
+      return doIndex(bulk, rowIt);
     }
   }
 
-  private static long doIndex(BulkIndexer bulk, Iterator<ProjectMeasuresDoc> docs) {
+  private static long doIndex(BulkIndexer bulk, Iterator<ProjectMeasures> docs) {
     bulk.start();
     long maxDate = 0L;
     while (docs.hasNext()) {
-      ProjectMeasuresDoc doc = docs.next();
-      bulk.add(newIndexRequest(doc));
+      ProjectMeasures doc = docs.next();
+      bulk.add(newIndexRequest(toProjectMeasuresDoc(doc)));
 
-      Date analysisDate = doc.getAnalysedAt();
+      Long analysisDate = doc.getProject().getAnalysisDate();
       // it's more efficient to sort programmatically than in SQL on some databases (MySQL for instance)
-      maxDate = Math.max(maxDate, analysisDate == null ? 0L : analysisDate.getTime());
+      maxDate = Math.max(maxDate, analysisDate == null ? 0L : analysisDate);
     }
     bulk.stop();
     return maxDate;
@@ -97,9 +97,21 @@ public class ProjectMeasuresIndexer extends BaseIndexer {
   }
 
   private static IndexRequest newIndexRequest(ProjectMeasuresDoc doc) {
-    return new IndexRequest(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, doc.getId())
-      .routing(doc.getId())
-      .parent(doc.getId())
+    String projectUuid = doc.getId();
+    return new IndexRequest(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, projectUuid)
+      .routing(projectUuid)
+      .parent(projectUuid)
       .source(doc.getFields());
   }
+
+  private static ProjectMeasuresDoc toProjectMeasuresDoc(ProjectMeasures projectMeasures) {
+    Long analysisDate = projectMeasures.getProject().getAnalysisDate();
+    return new ProjectMeasuresDoc()
+      .setId(projectMeasures.getProject().getUuid())
+      .setKey(projectMeasures.getProject().getKey())
+      .setName(projectMeasures.getProject().getName())
+      .setQualityGate(projectMeasures.getMeasures().getQualityGateStatus())
+      .setAnalysedAt(analysisDate == null ? null : new Date(analysisDate))
+      .setMeasuresFromMap(projectMeasures.getMeasures().getNumericMeasures());
+  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresResultSetIterator.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresResultSetIterator.java
deleted file mode 100644 (file)
index adffa8f..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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.server.component.es;
-
-import com.google.common.base.Joiner;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Scopes;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.ResultSetIterator;
-
-import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
-import static org.sonar.api.measures.Metric.ValueType.DATA;
-import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
-import static org.sonar.db.DatabaseUtils.repeatCondition;
-
-public class ProjectMeasuresResultSetIterator extends ResultSetIterator<ProjectMeasuresDoc> {
-
-  private static final Joiner METRICS_JOINER = Joiner.on("','");
-
-  private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, s.uuid, s.created_at FROM projects p " +
-    "LEFT OUTER JOIN snapshots s ON s.component_uuid=p.uuid AND s.islast=? " +
-    "WHERE p.enabled=? AND p.scope=? AND p.qualifier=?";
-
-  private static final String DATE_FILTER = " AND s.created_at>?";
-
-  private static final String PROJECT_FILTER = " AND p.uuid=?";
-
-  private static final String SQL_METRICS = "SELECT m.id, m.name FROM metrics m " +
-    "WHERE m.val_type NOT IN ('" + METRICS_JOINER.join(DATA.name(), DISTRIB.name()) + "') " +
-    "AND m.enabled=? AND m.hidden=?";
-
-  private static final String SQL_MEASURES = "SELECT pm.metric_id, pm.value, pm.variation_value_1, pm.text_value FROM project_measures pm " +
-    "WHERE pm.component_uuid = ? AND pm.analysis_uuid = ? " +
-    "AND pm.metric_id IN ({metricIds}) " +
-    "AND (pm.value IS NOT NULL OR pm.variation_value_1 IS NOT NULL OR pm.text_value IS NOT NULL) " +
-    "AND pm.person_id IS NULL ";
-
-  private final DbSession dbSession;
-  private final Map<Long, String> metrics;
-
-  private ProjectMeasuresResultSetIterator(PreparedStatement stmt, DbSession dbSession, Map<Long, String> metrics) throws SQLException {
-    super(stmt);
-    this.dbSession = dbSession;
-    this.metrics = metrics;
-  }
-
-  static ProjectMeasuresResultSetIterator create(DbClient dbClient, DbSession session, long afterDate, @Nullable String projectUuid) {
-    try {
-      PreparedStatement projectsStatement = createProjectsStatement(dbClient, session, afterDate, projectUuid);
-      Map<Long, String> metricIds = selectMetricIds(session);
-      return new ProjectMeasuresResultSetIterator(projectsStatement, session, metricIds);
-    } catch (SQLException e) {
-      throw new IllegalStateException("Fail to execute request to select all project measures", e);
-    }
-  }
-
-  private static PreparedStatement createProjectsStatement(DbClient dbClient, DbSession session, long afterDate, @Nullable String projectUuid) {
-    try {
-      String sql = SQL_PROJECTS;
-      sql += afterDate <= 0L ? "" : DATE_FILTER;
-      sql += projectUuid == null ? "" : PROJECT_FILTER;
-      PreparedStatement stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
-      stmt.setBoolean(1, true);
-      stmt.setBoolean(2, true);
-      stmt.setString(3, Scopes.PROJECT);
-      stmt.setString(4, Qualifiers.PROJECT);
-      int index = 5;
-      if (afterDate > 0L) {
-        stmt.setLong(index, afterDate);
-        index++;
-      }
-      if (projectUuid != null) {
-        stmt.setString(index, projectUuid);
-      }
-      return stmt;
-    } catch (SQLException e) {
-      throw new IllegalStateException("Fail to prepare SQL request to select all project measures", e);
-    }
-  }
-
-  private static Map<Long, String> selectMetricIds(DbSession session) {
-    Map<Long, String> metrics = new HashMap<>();
-    try (PreparedStatement stmt = createMetricsStatement(session);
-      ResultSet rs = stmt.executeQuery()) {
-      while (rs.next()) {
-        metrics.put(rs.getLong(1), rs.getString(2));
-      }
-      return metrics;
-    } catch (SQLException e) {
-      throw new IllegalStateException("Fail to execute request to select all metrics", e);
-    }
-  }
-
-  private static PreparedStatement createMetricsStatement(DbSession session) throws SQLException {
-    PreparedStatement stmt = session.getConnection().prepareStatement(SQL_METRICS);
-    stmt.setBoolean(1, true);
-    stmt.setBoolean(2, false);
-    return stmt;
-  }
-
-  @Override
-  protected ProjectMeasuresDoc read(ResultSet rs) throws SQLException {
-    String projectUuid = rs.getString(1);
-    Measures measures = selectMeasures(projectUuid, getString(rs, 4));
-    ProjectMeasuresDoc doc = new ProjectMeasuresDoc()
-      .setId(projectUuid)
-      .setKey(rs.getString(2))
-      .setName(rs.getString(3))
-      .setQualityGate(measures.getQualityGateStatus())
-      .setMeasuresFromMap(measures.getNumericMeasures());
-    long analysisDate = rs.getLong(5);
-    doc.setAnalysedAt(rs.wasNull() ? null : new Date(analysisDate));
-    return doc;
-  }
-
-  private Measures selectMeasures(String projectUuid, Optional<String> analysisUuid) {
-    Measures measures = new Measures();
-    if (!analysisUuid.isPresent()) {
-      return measures;
-    }
-    try (PreparedStatement stmt = createMeasuresStatement(projectUuid, analysisUuid.get());
-      ResultSet rs = stmt.executeQuery()) {
-      while (rs.next()) {
-        readMeasure(rs, measures);
-      }
-      return measures;
-    } catch (Exception e) {
-      throw new IllegalStateException(String.format("Fail to execute request to select measures of project %s, analysis %s", projectUuid, analysisUuid), e);
-    }
-  }
-
-  private void readMeasure(ResultSet rs, Measures measures) throws SQLException {
-    String metricKey = metrics.get(rs.getLong(1));
-    Optional<Double> value = metricKey.startsWith("new_") ? getDouble(rs, 3) : getDouble(rs, 2);
-    if (value.isPresent()) {
-      measures.addNumericMeasure(metricKey, value.get());
-      return;
-    } else if (ALERT_STATUS_KEY.equals(metricKey)) {
-      String textValue = rs.getString(4);
-      if (!rs.wasNull()) {
-        measures.setQualityGateStatus(textValue);
-        return;
-      }
-    }
-    throw new IllegalArgumentException("Measure has no value");
-  }
-
-  private PreparedStatement createMeasuresStatement(String projectUuid, String analysisUuid) throws SQLException {
-    String sql = StringUtils.replace(SQL_MEASURES, "{metricIds}", repeatCondition("?", metrics.size(), ","));
-    PreparedStatement stmt = dbSession.getConnection().prepareStatement(sql);
-    stmt.setString(1, projectUuid);
-    stmt.setString(2, analysisUuid);
-    int index = 3;
-    for (Long metricId : metrics.keySet()) {
-      stmt.setLong(index, metricId);
-      index++;
-    }
-    return stmt;
-  }
-
-  private static Optional<Double> getDouble(ResultSet rs, int index) {
-    try {
-      Double value = rs.getDouble(index);
-      if (!rs.wasNull()) {
-        return Optional.of(value);
-      }
-      return Optional.empty();
-    } catch (SQLException e) {
-      throw new IllegalStateException("Fail to get double value", e);
-    }
-  }
-
-  private static Optional<String> getString(ResultSet rs, int index) {
-    try {
-      String value = rs.getString(index);
-      if (!rs.wasNull()) {
-        return Optional.of(value);
-      }
-      return Optional.empty();
-    } catch (SQLException e) {
-      throw new IllegalStateException("Fail to get string value", e);
-    }
-  }
-
-  private static class Measures {
-    private Map<String, Object> numericMeasures = new HashMap<>();
-    private String qualityGateStatus;
-
-    Measures addNumericMeasure(String metricKey, double value) {
-      numericMeasures.put(metricKey, value);
-      return this;
-    }
-
-    public Map<String, Object> getNumericMeasures() {
-      return numericMeasures;
-    }
-
-    Measures setQualityGateStatus(@Nullable String qualityGateStatus) {
-      this.qualityGateStatus = qualityGateStatus;
-      return this;
-    }
-
-    @CheckForNull
-    public String getQualityGateStatus() {
-      return qualityGateStatus;
-    }
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresResultSetIteratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresResultSetIteratorTest.java
deleted file mode 100644 (file)
index 09086ad..0000000
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * 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.server.component.es;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import java.util.Date;
-import java.util.Map;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.measure.MeasureDto;
-import org.sonar.db.measure.MeasureTesting;
-import org.sonar.db.metric.MetricDto;
-import org.sonar.db.metric.MetricTesting;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.measures.Metric.ValueType.DATA;
-import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
-import static org.sonar.api.measures.Metric.ValueType.INT;
-import static org.sonar.api.measures.Metric.ValueType.LEVEL;
-import static org.sonar.db.component.ComponentTesting.newDeveloper;
-import static org.sonar.db.component.ComponentTesting.newProjectDto;
-import static org.sonar.db.component.ComponentTesting.newView;
-import static org.sonar.db.component.SnapshotTesting.newAnalysis;
-import static org.sonar.server.computation.task.projectanalysis.measure.Measure.Level.WARN;
-
-public class ProjectMeasuresResultSetIteratorTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  DbClient dbClient = dbTester.getDbClient();
-  DbSession dbSession = dbTester.getSession();
-
-  ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
-
-  @Test
-  public void return_project_measure() {
-    MetricDto metric1 = insertIntMetric("ncloc");
-    MetricDto metric2 = insertIntMetric("coverage");
-    ComponentDto project = newProjectDto().setKey("Project-Key").setName("Project Name");
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-    insertMeasure(project, analysis, metric1, 10d);
-    insertMeasure(project, analysis, metric2, 20d);
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
-
-    assertThat(docsById).hasSize(1);
-    ProjectMeasuresDoc doc = docsById.get(project.uuid());
-    assertThat(doc).isNotNull();
-    assertThat(doc.getId()).isEqualTo(project.uuid());
-    assertThat(doc.getKey()).isEqualTo("Project-Key");
-    assertThat(doc.getName()).isEqualTo("Project Name");
-    assertThat(doc.getAnalysedAt()).isNotNull().isEqualTo(new Date(analysis.getCreatedAt()));
-    assertThat(doc.getMeasures()).containsOnly(
-      ImmutableMap.of("key", "ncloc", "value", 10d),
-      ImmutableMap.of("key", "coverage", "value", 20d));
-  }
-
-  @Test
-  public void return_project_measure_having_leak() throws Exception {
-    MetricDto metric = insertIntMetric("new_lines");
-    ComponentDto project = newProjectDto();
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-    insertMeasureOnLeak(project, analysis, metric, 10d);
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
-
-    assertThat(docsById).hasSize(1);
-    ProjectMeasuresDoc doc = docsById.get(project.uuid());
-    assertThat(doc).isNotNull();
-    assertThat(doc.getMeasures()).containsOnly(ImmutableMap.of("key", "new_lines", "value", 10d));
-  }
-
-  @Test
-  public void return_quality_gate_status_measure() throws Exception {
-    MetricDto metric = insertMetric("alert_status", LEVEL);
-    ComponentDto project = newProjectDto();
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-    insertMeasure(project, analysis, metric, WARN.name());
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
-
-    assertThat(docsById).hasSize(1);
-    assertThat(docsById.get(project.uuid()).getQualityGate()).isEqualTo("WARN");
-  }
-
-  @Test
-  public void does_not_return_none_numeric_metrics() throws Exception {
-    MetricDto dataMetric = insertMetric("data", DATA);
-    MetricDto distribMetric = insertMetric("distrib", DISTRIB);
-    ComponentDto project = newProjectDto();
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-    insertMeasure(project, analysis, dataMetric, 10d);
-    insertMeasure(project, analysis, distribMetric, 10d);
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
-    assertThat(docsById).hasSize(1);
-    ProjectMeasuresDoc doc = docsById.get(project.uuid());
-    assertThat(doc.getMeasures()).isEmpty();
-  }
-
-  @Test
-  public void does_not_return_disabled_and_hidden_metrics() throws Exception {
-    MetricDto disabledMetric = insertMetric("disabled", false, false, INT);
-    MetricDto hiddenMetric = insertMetric("hidden", true, true, INT);
-    ComponentDto project = newProjectDto();
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-    insertMeasure(project, analysis, disabledMetric, 10d);
-    insertMeasure(project, analysis, hiddenMetric, 10d);
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
-    assertThat(docsById).hasSize(1);
-    ProjectMeasuresDoc doc = docsById.get(project.uuid());
-    assertThat(doc.getMeasures()).isEmpty();
-  }
-
-  @Test
-  public void fail_when_measure_return_no_value() throws Exception {
-    MetricDto metric = insertIntMetric("new_lines");
-    ComponentDto project = newProjectDto();
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-    insertMeasure(project, analysis, metric, 10d);
-
-    expectedException.expect(IllegalStateException.class);
-    createResultSetAndReturnDocsById();
-  }
-
-  @Test
-  public void return_many_project_measures() {
-    componentDbTester.insertProjectAndSnapshot(newProjectDto());
-    componentDbTester.insertProjectAndSnapshot(newProjectDto());
-    componentDbTester.insertProjectAndSnapshot(newProjectDto());
-
-    assertThat(createResultSetAndReturnDocsById()).hasSize(3);
-  }
-
-  @Test
-  public void return_project_without_analysis() throws Exception {
-    ComponentDto project = componentDbTester.insertComponent(newProjectDto());
-    dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setLast(false));
-    dbSession.commit();
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById();
-
-    assertThat(docsById).hasSize(1);
-    ProjectMeasuresDoc doc = docsById.get(project.uuid());
-    assertThat(doc.getAnalysedAt()).isNull();
-  }
-
-  @Test
-  public void does_not_return_non_active_projects() throws Exception {
-    // Disabled project
-    componentDbTester.insertProjectAndSnapshot(newProjectDto().setEnabled(false));
-    // Disabled project with analysis
-    ComponentDto project = componentDbTester.insertComponent(newProjectDto().setEnabled(false));
-    dbClient.snapshotDao().insert(dbSession, newAnalysis(project));
-
-    // A view
-    componentDbTester.insertProjectAndSnapshot(newView());
-
-    // A developer
-    componentDbTester.insertProjectAndSnapshot(newDeveloper("dev"));
-
-    dbSession.commit();
-
-    assertResultSetIsEmpty();
-  }
-
-  @Test
-  public void return_only_docs_from_given_project() throws Exception {
-    ComponentDto project = newProjectDto();
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-    componentDbTester.insertProjectAndSnapshot(newProjectDto());
-    componentDbTester.insertProjectAndSnapshot(newProjectDto());
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById(0L, project.uuid());
-
-    assertThat(docsById).hasSize(1);
-    ProjectMeasuresDoc doc = docsById.get(project.uuid());
-    assertThat(doc).isNotNull();
-    assertThat(doc.getId()).isEqualTo(project.uuid());
-    assertThat(doc.getKey()).isNotNull().isEqualTo(project.getKey());
-    assertThat(doc.getName()).isNotNull().isEqualTo(project.name());
-    assertThat(doc.getAnalysedAt()).isNotNull().isEqualTo(new Date(analysis.getCreatedAt()));
-  }
-
-  @Test
-  public void return_only_docs_after_date() throws Exception {
-    ComponentDto project1 = newProjectDto();
-    dbClient.componentDao().insert(dbSession, project1);
-    dbClient.snapshotDao().insert(dbSession, newAnalysis(project1).setCreatedAt(1_000_000L));
-    ComponentDto project2 = newProjectDto();
-    dbClient.componentDao().insert(dbSession, project2);
-    dbClient.snapshotDao().insert(dbSession, newAnalysis(project2).setCreatedAt(2_000_000L));
-    dbSession.commit();
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById(1_500_000L, null);
-
-    assertThat(docsById).hasSize(1);
-    assertThat(docsById.get(project2.uuid())).isNotNull();
-  }
-
-  @Test
-  public void return_nothing_on_unknown_project() throws Exception {
-    componentDbTester.insertProjectAndSnapshot(newProjectDto());
-
-    Map<String, ProjectMeasuresDoc> docsById = createResultSetAndReturnDocsById(0L, "UNKNOWN");
-
-    assertThat(docsById).isEmpty();
-  }
-
-  private Map<String, ProjectMeasuresDoc> createResultSetAndReturnDocsById() {
-    return createResultSetAndReturnDocsById(0L, null);
-  }
-
-  private Map<String, ProjectMeasuresDoc> createResultSetAndReturnDocsById(long date, @Nullable String projectUuid) {
-    ProjectMeasuresResultSetIterator it = ProjectMeasuresResultSetIterator.create(dbTester.getDbClient(), dbTester.getSession(), date, projectUuid);
-    Map<String, ProjectMeasuresDoc> docsById = Maps.uniqueIndex(it, ProjectMeasuresDoc::getId);
-    it.close();
-    return docsById;
-  }
-
-  private void assertResultSetIsEmpty() {
-    assertThat(createResultSetAndReturnDocsById()).isEmpty();
-  }
-
-  private MetricDto insertIntMetric(String metricKey) {
-    return insertMetric(metricKey, true, false, INT);
-  }
-
-  private MetricDto insertMetric(String metricKey, Metric.ValueType type) {
-    return insertMetric(metricKey, true, false, type);
-  }
-
-  private MetricDto insertMetric(String metricKey, boolean enabled, boolean hidden, Metric.ValueType type) {
-    MetricDto metric = dbClient.metricDao().insert(dbSession,
-      MetricTesting.newMetricDto()
-        .setKey(metricKey)
-        .setEnabled(enabled)
-        .setHidden(hidden)
-        .setValueType(type.name()));
-    dbSession.commit();
-    return metric;
-  }
-
-  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, double value) {
-    return insertMeasure(project, analysis, metric, value, null);
-  }
-
-  private MeasureDto insertMeasureOnLeak(ComponentDto project, SnapshotDto analysis, MetricDto metric, double value) {
-    return insertMeasure(project, analysis, metric, null, value);
-  }
-
-  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, String value) {
-    return insertMeasure(MeasureTesting.newMeasureDto(metric, project, analysis).setData(value));
-  }
-
-  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, @Nullable Double value, @Nullable Double leakValue) {
-    return insertMeasure(MeasureTesting.newMeasureDto(metric, project, analysis).setValue(value).setVariation(1, leakValue));
-  }
-
-  private MeasureDto insertMeasure(MeasureDto measure) {
-    dbClient.measureDao().insert(dbSession, measure);
-    dbSession.commit();
-    return measure;
-  }
-
-}
diff --git a/sonar-db/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java b/sonar-db/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java
new file mode 100644 (file)
index 0000000..b61b385
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * 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 com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.db.DbSession;
+
+import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
+import static org.sonar.api.measures.Metric.ValueType.BOOL;
+import static org.sonar.api.measures.Metric.ValueType.FLOAT;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.LEVEL;
+import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
+import static org.sonar.api.measures.Metric.ValueType.PERCENT;
+import static org.sonar.api.measures.Metric.ValueType.RATING;
+import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
+import static org.sonar.db.DatabaseUtils.repeatCondition;
+
+public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMeasuresIndexerIterator.ProjectMeasures> {
+
+  private static final Set<String> METRIC_TYPES = ImmutableSet.of(INT.name(), FLOAT.name(), PERCENT.name(), BOOL.name(), MILLISEC.name(), LEVEL.name(), RATING.name(),
+    WORK_DUR.name());
+
+  private static final Joiner METRICS_JOINER = Joiner.on("','");
+
+  private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, s.uuid, s.created_at FROM projects p " +
+    "LEFT OUTER JOIN snapshots s ON s.component_uuid=p.uuid AND s.islast=? " +
+    "WHERE p.enabled=? AND p.scope=? AND p.qualifier=?";
+
+  private static final String DATE_FILTER = " AND s.created_at>?";
+
+  private static final String PROJECT_FILTER = " AND p.uuid=?";
+
+  private static final String SQL_METRICS = "SELECT m.id, m.name FROM metrics m " +
+    "WHERE m.val_type IN ('" + METRICS_JOINER.join(METRIC_TYPES) + "') " +
+    "AND m.enabled=?";
+
+  private static final String SQL_MEASURES = "SELECT pm.metric_id, pm.value, pm.variation_value_1, pm.text_value FROM project_measures pm " +
+    "WHERE pm.component_uuid = ? AND pm.analysis_uuid = ? " +
+    "AND pm.metric_id IN ({metricIds}) " +
+    "AND (pm.value IS NOT NULL OR pm.variation_value_1 IS NOT NULL OR pm.text_value IS NOT NULL) " +
+    "AND pm.person_id IS NULL ";
+
+  private final PreparedStatement measuresStatement;
+  private final Map<Long, String> metricKeysByIds;
+  private final Iterator<Project> projects;
+
+  private ProjectMeasuresIndexerIterator(PreparedStatement measuresStatement, Map<Long, String> metricKeysByIds, List<Project> projects) throws SQLException {
+    this.measuresStatement = measuresStatement;
+    this.metricKeysByIds = metricKeysByIds;
+    this.projects = projects.iterator();
+  }
+
+  public static ProjectMeasuresIndexerIterator create(DbSession session, long afterDate, @Nullable String projectUuid) {
+    try {
+      Map<Long, String> metrics = selectMetricKeysByIds(session);
+      List<Project> projects = selectProjects(session, afterDate, projectUuid);
+      PreparedStatement projectsStatement = createMeasuresStatement(session, metrics.keySet());
+      return new ProjectMeasuresIndexerIterator(projectsStatement, metrics, projects);
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to execute request to select all project measures", e);
+    }
+  }
+
+  private static Map<Long, String> selectMetricKeysByIds(DbSession session) {
+    Map<Long, String> metrics = new HashMap<>();
+    try (PreparedStatement stmt = createMetricsStatement(session);
+      ResultSet rs = stmt.executeQuery()) {
+      while (rs.next()) {
+        metrics.put(rs.getLong(1), rs.getString(2));
+      }
+      return metrics;
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to execute request to select all metrics", e);
+    }
+  }
+
+  private static PreparedStatement createMetricsStatement(DbSession session) throws SQLException {
+    PreparedStatement stmt = session.getConnection().prepareStatement(SQL_METRICS);
+    stmt.setBoolean(1, true);
+    return stmt;
+  }
+
+  private static List<Project> selectProjects(DbSession session, long afterDate, @Nullable String projectUuid) {
+    List<Project> projects = new ArrayList<>();
+    try (PreparedStatement stmt = createProjectsStatement(session, afterDate, projectUuid);
+      ResultSet rs = stmt.executeQuery()) {
+      while (rs.next()) {
+        long analysisDate = rs.getLong(5);
+        Project project = new Project(rs.getString(1), rs.getString(2), rs.getString(3), getString(rs, 4).orElseGet(() -> null), rs.wasNull() ? null : analysisDate);
+        projects.add(project);
+      }
+      return projects;
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to execute request to select all projects", e);
+    }
+  }
+
+  private static PreparedStatement createProjectsStatement(DbSession session, long afterDate, @Nullable String projectUuid) {
+    try {
+      String sql = SQL_PROJECTS;
+      sql += afterDate <= 0L ? "" : DATE_FILTER;
+      sql += projectUuid == null ? "" : PROJECT_FILTER;
+      PreparedStatement stmt = session.getConnection().prepareStatement(sql);
+      stmt.setBoolean(1, true);
+      stmt.setBoolean(2, true);
+      stmt.setString(3, Scopes.PROJECT);
+      stmt.setString(4, Qualifiers.PROJECT);
+      int index = 5;
+      if (afterDate > 0L) {
+        stmt.setLong(index, afterDate);
+        index++;
+      }
+      if (projectUuid != null) {
+        stmt.setString(index, projectUuid);
+      }
+      return stmt;
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to prepare SQL request to select all project measures", e);
+    }
+  }
+
+  private static PreparedStatement createMeasuresStatement(DbSession session, Set<Long> metricIds) throws SQLException {
+    try {
+      String sql = StringUtils.replace(SQL_MEASURES, "{metricIds}", repeatCondition("?", metricIds.size(), ","));
+      PreparedStatement stmt = session.getConnection().prepareStatement(sql);
+      int index = 3;
+      for (Long metricId : metricIds) {
+        stmt.setLong(index, metricId);
+        index++;
+      }
+      return stmt;
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to prepare SQL request to select measures", e);
+    }
+  }
+
+  @Override
+  @CheckForNull
+  protected ProjectMeasures doNext() {
+    if (!projects.hasNext()) {
+      return null;
+    }
+    Project project = projects.next();
+    Measures measures = selectMeasures(project.getUuid(), project.getAnalysisUuid());
+    return new ProjectMeasures(project, measures);
+  }
+
+  private Measures selectMeasures(String projectUuid, @Nullable String analysisUuid) {
+    Measures measures = new Measures();
+    if (analysisUuid == null || metricKeysByIds.isEmpty()) {
+      return measures;
+    }
+    ResultSet rs = null;
+    try {
+      measuresStatement.setString(1, projectUuid);
+      measuresStatement.setString(2, analysisUuid);
+      rs = measuresStatement.executeQuery();
+      while (rs.next()) {
+        readMeasure(rs, measures);
+      }
+      return measures;
+    } catch (Exception e) {
+      throw new IllegalStateException(String.format("Fail to execute request to select measures of project %s, analysis %s", projectUuid, analysisUuid), e);
+    } finally {
+      DatabaseUtils.closeQuietly(rs);
+    }
+  }
+
+  private void readMeasure(ResultSet rs, Measures measures) throws SQLException {
+    String metricKey = metricKeysByIds.get(rs.getLong(1));
+    Optional<Double> value = metricKey.startsWith("new_") ? getDouble(rs, 3) : getDouble(rs, 2);
+    if (value.isPresent()) {
+      measures.addNumericMeasure(metricKey, value.get());
+      return;
+    } else if (ALERT_STATUS_KEY.equals(metricKey)) {
+      String textValue = rs.getString(4);
+      if (!rs.wasNull()) {
+        measures.setQualityGateStatus(textValue);
+        return;
+      }
+    }
+    throw new IllegalArgumentException("Measure has no value");
+  }
+
+  @Override
+  protected void doClose() throws Exception {
+    measuresStatement.close();
+  }
+
+  private static Optional<Double> getDouble(ResultSet rs, int index) {
+    try {
+      Double value = rs.getDouble(index);
+      if (!rs.wasNull()) {
+        return Optional.of(value);
+      }
+      return Optional.empty();
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to get double value", e);
+    }
+  }
+
+  private static Optional<String> getString(ResultSet rs, int index) {
+    try {
+      String value = rs.getString(index);
+      if (!rs.wasNull()) {
+        return Optional.of(value);
+      }
+      return Optional.empty();
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to get string value", e);
+    }
+  }
+
+  public static class Project {
+    private final String uuid;
+    private final String key;
+    private final String name;
+    private final String analysisUuid;
+    private final Long analysisDate;
+
+    public Project(String uuid, String key, String name, @Nullable String analysisUuid, @Nullable Long analysisDate) {
+      this.uuid = uuid;
+      this.key = key;
+      this.name = name;
+      this.analysisUuid = analysisUuid;
+      this.analysisDate = analysisDate;
+    }
+
+    public String getUuid() {
+      return uuid;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    @CheckForNull
+    public String getAnalysisUuid() {
+      return analysisUuid;
+    }
+
+    @CheckForNull
+    public Long getAnalysisDate() {
+      return analysisDate;
+    }
+  }
+
+  public static class Measures {
+
+    private Map<String, Double> numericMeasures = new HashMap<>();
+    private String qualityGateStatus;
+
+    Measures addNumericMeasure(String metricKey, double value) {
+      numericMeasures.put(metricKey, value);
+      return this;
+    }
+
+    public Map<String, Double> getNumericMeasures() {
+      return numericMeasures;
+    }
+
+    Measures setQualityGateStatus(@Nullable String qualityGateStatus) {
+      this.qualityGateStatus = qualityGateStatus;
+      return this;
+    }
+
+    @CheckForNull
+    public String getQualityGateStatus() {
+      return qualityGateStatus;
+    }
+  }
+
+  public static class ProjectMeasures {
+    private Project project;
+    private Measures measures;
+
+    public ProjectMeasures(Project project, Measures measures) {
+      this.project = project;
+      this.measures = measures;
+    }
+
+    public Project getProject() {
+      return project;
+    }
+
+    public Measures getMeasures() {
+      return measures;
+    }
+
+  }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java b/sonar-db/src/test/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorTest.java
new file mode 100644 (file)
index 0000000..69763cf
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * 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 com.google.common.collect.Maps;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.measure.ProjectMeasuresIndexerIterator.ProjectMeasures;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.metric.MetricTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.sonar.api.measures.Metric.Level.WARN;
+import static org.sonar.api.measures.Metric.ValueType.DATA;
+import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.LEVEL;
+import static org.sonar.api.measures.Metric.ValueType.STRING;
+import static org.sonar.db.component.ComponentTesting.newDeveloper;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.component.ComponentTesting.newView;
+import static org.sonar.db.component.SnapshotTesting.newAnalysis;
+
+public class ProjectMeasuresIndexerIteratorTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  DbClient dbClient = dbTester.getDbClient();
+  DbSession dbSession = dbTester.getSession();
+
+  ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+
+  @Test
+  public void return_project_measure() {
+    MetricDto metric1 = insertIntMetric("ncloc");
+    MetricDto metric2 = insertIntMetric("coverage");
+    ComponentDto project = newProjectDto().setKey("Project-Key").setName("Project Name");
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, metric1, 10d);
+    insertMeasure(project, analysis, metric2, 20d);
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById();
+
+    assertThat(docsById).hasSize(1);
+    ProjectMeasures doc = docsById.get(project.uuid());
+    assertThat(doc).isNotNull();
+    assertThat(doc.getProject().getUuid()).isEqualTo(project.uuid());
+    assertThat(doc.getProject().getKey()).isEqualTo("Project-Key");
+    assertThat(doc.getProject().getName()).isEqualTo("Project Name");
+    assertThat(doc.getProject().getAnalysisDate()).isNotNull().isEqualTo(analysis.getCreatedAt());
+    assertThat(doc.getMeasures().getNumericMeasures()).containsOnly(entry("ncloc", 10d), entry("coverage", 20d));
+  }
+
+  @Test
+  public void return_project_measure_having_leak() throws Exception {
+    MetricDto metric = insertIntMetric("new_lines");
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasureOnLeak(project, analysis, metric, 10d);
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById();
+
+    assertThat(docsById.get(project.uuid()).getMeasures().getNumericMeasures()).containsOnly(entry("new_lines", 10d));
+  }
+
+  @Test
+  public void return_quality_gate_status_measure() throws Exception {
+    MetricDto metric = insertMetric("alert_status", LEVEL);
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, metric, WARN.name());
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById();
+
+    assertThat(docsById.get(project.uuid()).getMeasures().getQualityGateStatus()).isEqualTo("WARN");
+  }
+
+  @Test
+  public void does_not_return_none_numeric_metrics() throws Exception {
+    MetricDto dataMetric = insertMetric("data", DATA);
+    MetricDto distribMetric = insertMetric("distrib", DISTRIB);
+    MetricDto stringMetric = insertMetric("string", STRING);
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, dataMetric, "dat");
+    insertMeasure(project, analysis, distribMetric, "dis");
+    insertMeasure(project, analysis, stringMetric, "str");
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById();
+
+    assertThat(docsById.get(project.uuid()).getMeasures().getNumericMeasures()).isEmpty();
+  }
+
+  @Test
+  public void does_not_return_disabled_metrics() throws Exception {
+    MetricDto disabledMetric = insertMetric("disabled", false, false, INT);
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, disabledMetric, 10d);
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById();
+
+    assertThat(docsById.get(project.uuid()).getMeasures().getNumericMeasures()).isEmpty();
+  }
+
+  @Test
+  public void fail_when_measure_return_no_value() throws Exception {
+    MetricDto metric = insertIntMetric("new_lines");
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    insertMeasure(project, analysis, metric, 10d);
+
+    expectedException.expect(IllegalStateException.class);
+    createResultSetAndReturnDocsById();
+  }
+
+  @Test
+  public void return_many_project_measures() {
+    componentDbTester.insertProjectAndSnapshot(newProjectDto());
+    componentDbTester.insertProjectAndSnapshot(newProjectDto());
+    componentDbTester.insertProjectAndSnapshot(newProjectDto());
+
+    assertThat(createResultSetAndReturnDocsById()).hasSize(3);
+  }
+
+  @Test
+  public void return_project_without_analysis() throws Exception {
+    ComponentDto project = componentDbTester.insertComponent(newProjectDto());
+    dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setLast(false));
+    dbSession.commit();
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById();
+
+    assertThat(docsById).hasSize(1);
+    ProjectMeasures doc = docsById.get(project.uuid());
+    assertThat(doc.getProject().getAnalysisDate()).isNull();
+  }
+
+  @Test
+  public void does_not_return_non_active_projects() throws Exception {
+    // Disabled project
+    componentDbTester.insertProjectAndSnapshot(newProjectDto().setEnabled(false));
+    // Disabled project with analysis
+    ComponentDto project = componentDbTester.insertComponent(newProjectDto().setEnabled(false));
+    dbClient.snapshotDao().insert(dbSession, newAnalysis(project));
+
+    // A view
+    componentDbTester.insertProjectAndSnapshot(newView());
+
+    // A developer
+    componentDbTester.insertProjectAndSnapshot(newDeveloper("dev"));
+
+    dbSession.commit();
+
+    assertResultSetIsEmpty();
+  }
+
+  @Test
+  public void return_only_docs_from_given_project() throws Exception {
+    ComponentDto project = newProjectDto();
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+    componentDbTester.insertProjectAndSnapshot(newProjectDto());
+    componentDbTester.insertProjectAndSnapshot(newProjectDto());
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById(0L, project.uuid());
+
+    assertThat(docsById).hasSize(1);
+    ProjectMeasures doc = docsById.get(project.uuid());
+    assertThat(doc).isNotNull();
+    assertThat(doc.getProject().getUuid()).isEqualTo(project.uuid());
+    assertThat(doc.getProject().getKey()).isNotNull().isEqualTo(project.getKey());
+    assertThat(doc.getProject().getName()).isNotNull().isEqualTo(project.name());
+    assertThat(doc.getProject().getAnalysisDate()).isNotNull().isEqualTo(analysis.getCreatedAt());
+  }
+
+  @Test
+  public void return_only_docs_after_date() throws Exception {
+    ComponentDto project1 = newProjectDto();
+    dbClient.componentDao().insert(dbSession, project1);
+    dbClient.snapshotDao().insert(dbSession, newAnalysis(project1).setCreatedAt(1_000_000L));
+    ComponentDto project2 = newProjectDto();
+    dbClient.componentDao().insert(dbSession, project2);
+    dbClient.snapshotDao().insert(dbSession, newAnalysis(project2).setCreatedAt(2_000_000L));
+    dbSession.commit();
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById(1_500_000L, null);
+
+    assertThat(docsById).hasSize(1);
+    assertThat(docsById.get(project2.uuid())).isNotNull();
+  }
+
+  @Test
+  public void return_nothing_on_unknown_project() throws Exception {
+    componentDbTester.insertProjectAndSnapshot(newProjectDto());
+
+    Map<String, ProjectMeasures> docsById = createResultSetAndReturnDocsById(0L, "UNKNOWN");
+
+    assertThat(docsById).isEmpty();
+  }
+
+  private Map<String, ProjectMeasures> createResultSetAndReturnDocsById() {
+    return createResultSetAndReturnDocsById(0L, null);
+  }
+
+  private Map<String, ProjectMeasures> createResultSetAndReturnDocsById(long date, @Nullable String projectUuid) {
+    ProjectMeasuresIndexerIterator it = ProjectMeasuresIndexerIterator.create(dbTester.getSession(), date, projectUuid);
+    Map<String, ProjectMeasures> docsById = Maps.uniqueIndex(it, pm -> pm.getProject().getUuid());
+    it.close();
+    return docsById;
+  }
+
+  private void assertResultSetIsEmpty() {
+    assertThat(createResultSetAndReturnDocsById()).isEmpty();
+  }
+
+  private MetricDto insertIntMetric(String metricKey) {
+    return insertMetric(metricKey, true, false, INT);
+  }
+
+  private MetricDto insertMetric(String metricKey, Metric.ValueType type) {
+    return insertMetric(metricKey, true, false, type);
+  }
+
+  private MetricDto insertMetric(String metricKey, boolean enabled, boolean hidden, Metric.ValueType type) {
+    MetricDto metric = dbClient.metricDao().insert(dbSession,
+      MetricTesting.newMetricDto()
+        .setKey(metricKey)
+        .setEnabled(enabled)
+        .setHidden(hidden)
+        .setValueType(type.name()));
+    dbSession.commit();
+    return metric;
+  }
+
+  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, double value) {
+    return insertMeasure(project, analysis, metric, value, null);
+  }
+
+  private MeasureDto insertMeasureOnLeak(ComponentDto project, SnapshotDto analysis, MetricDto metric, double value) {
+    return insertMeasure(project, analysis, metric, null, value);
+  }
+
+  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, String value) {
+    return insertMeasure(MeasureTesting.newMeasureDto(metric, project, analysis).setData(value));
+  }
+
+  private MeasureDto insertMeasure(ComponentDto project, SnapshotDto analysis, MetricDto metric, @Nullable Double value, @Nullable Double leakValue) {
+    return insertMeasure(MeasureTesting.newMeasureDto(metric, project, analysis).setValue(value).setVariation(1, leakValue));
+  }
+
+  private MeasureDto insertMeasure(MeasureDto measure) {
+    dbClient.measureDao().insert(dbSession, measure);
+    dbSession.commit();
+    return measure;
+  }
+
+}
index 425777eada6367dcdd1da825b2056d2591321348..2fdfbff01f221ec920f6fc8f8244235e488e4d0f 100644 (file)
@@ -29,8 +29,8 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
-import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.server.ServerSide;
 
@@ -223,7 +223,7 @@ public class Metric<G extends Serializable> implements Serializable, org.sonar.a
    * @param userManaged whether the metric is user managed
    */
   private Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, @Nullable String domain,
-    boolean userManaged) {
+                 boolean userManaged) {
     this.key = key;
     this.description = description;
     this.type = type;