]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7789 Add and populate DB column RULES_PROFILES.LAST_USED 1060/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 22 Jun 2016 09:38:41 +0000 (11:38 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 24 Jun 2016 09:00:24 +0000 (11:00 +0200)
27 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/UpdateQualityProfilesLastUsedDateStep.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/UpdateQualityProfilesLastUsedDateStepTest.java [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1246_add_last_used_column_to_rules_profiles.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1247_populate_last_used_of_rules_profiles.rb [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileDto.java
sonar-db/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java
sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
sonar-db/src/main/java/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfiles.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfiles.java [new file with mode: 0644]
sonar-db/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml
sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl
sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileDaoTest.java
sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileDbTester.java
sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileTesting.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java
sonar-db/src/test/java/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfilesTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfilesTest.java [new file with mode: 0644]
sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/delete-result.xml
sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/insert-result.xml
sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/shared.xml
sonar-db/src/test/resources/org/sonar/db/qualityprofile/QualityProfileDaoTest/update-result.xml
sonar-db/src/test/resources/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfilesTest/rules_profiles.sql [new file with mode: 0644]
sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfilesTest/rules_profiles.sql [new file with mode: 0644]

index fa6c1a570bca1beac5b46ac1c5dad21bc58dc3d9..1d129631367e8afc200a8e7db1da3c80042c831e 100644 (file)
@@ -98,6 +98,7 @@ public class ReportComputationSteps extends AbstractComputationSteps {
 
     // Switch snapshot and purge
     SwitchSnapshotStep.class,
+    UpdateQualityProfilesLastUsedDateStep.class,
     IndexComponentsStep.class,
     PurgeDatastoresStep.class,
     ApplyPermissionsStep.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/UpdateQualityProfilesLastUsedDateStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/UpdateQualityProfilesLastUsedDateStep.java
new file mode 100644 (file)
index 0000000..1f6a47b
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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.computation.step;
+
+import com.google.common.base.Optional;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.QualityProfileDto;
+import org.sonar.server.computation.analysis.AnalysisMetadataHolder;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.TreeRootHolder;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepository;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricRepository;
+import org.sonar.server.computation.qualityprofile.QPMeasureData;
+import org.sonar.server.computation.qualityprofile.QualityProfile;
+
+import static java.util.Collections.emptySet;
+
+public class UpdateQualityProfilesLastUsedDateStep implements ComputationStep {
+
+  private final DbClient dbClient;
+  private final AnalysisMetadataHolder analysisMetadataHolder;
+  private final TreeRootHolder treeRootHolder;
+  private final MetricRepository metricRepository;
+  private final MeasureRepository measureRepository;
+
+  public UpdateQualityProfilesLastUsedDateStep(DbClient dbClient, AnalysisMetadataHolder analysisMetadataHolder, TreeRootHolder treeRootHolder, MetricRepository metricRepository,
+    MeasureRepository measureRepository) {
+    this.dbClient = dbClient;
+    this.analysisMetadataHolder = analysisMetadataHolder;
+    this.treeRootHolder = treeRootHolder;
+    this.metricRepository = metricRepository;
+    this.measureRepository = measureRepository;
+  }
+
+  @Override
+  public void execute() {
+    DbSession dbSession = dbClient.openSession(true);
+    try {
+      Component root = treeRootHolder.getRoot();
+      Metric metric = metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY);
+      Set<QualityProfile> qualityProfiles = parseQualityProfiles(measureRepository.getRawMeasure(root, metric));
+      if (qualityProfiles.isEmpty()) {
+        return;
+      }
+
+      List<QualityProfileDto> dtos = dbClient.qualityProfileDao().selectByKeys(dbSession, qualityProfiles.stream().map(QualityProfile::getQpKey).collect(Collectors.toList()));
+      long analysisDate = analysisMetadataHolder.getAnalysisDate();
+      dtos.forEach(dto -> {
+        dto.setLastUsed(analysisDate);
+        dbClient.qualityProfileDao().update(dbSession, dto);
+      });
+
+      dbSession.commit();
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
+  }
+
+  @Override
+  public String getDescription() {
+    return "Update quality profiles";
+  }
+
+  private static Set<QualityProfile> parseQualityProfiles(Optional<Measure> measure) {
+    if (!measure.isPresent()) {
+      return emptySet();
+    }
+
+    String data = measure.get().getStringValue();
+    return data == null ? emptySet() : QPMeasureData.fromJson(data).getProfiles();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/UpdateQualityProfilesLastUsedDateStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/UpdateQualityProfilesLastUsedDateStepTest.java
new file mode 100644 (file)
index 0000000..a3dcdca
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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.computation.step;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
+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.qualityprofile.QualityProfileDbTester;
+import org.sonar.db.qualityprofile.QualityProfileDto;
+import org.sonar.server.computation.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ReportComponent;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.metric.Metric.MetricType;
+import org.sonar.server.computation.metric.MetricImpl;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+import org.sonar.server.computation.qualityprofile.QPMeasureData;
+import org.sonar.server.computation.qualityprofile.QualityProfile;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES_KEY;
+import static org.sonar.db.qualityprofile.QualityProfileTesting.newQualityProfileDto;
+
+public class UpdateQualityProfilesLastUsedDateStepTest {
+  static final long ANALYSIS_DATE = 1_123_456_789L;
+  private static final Component PROJECT = ReportComponent.DUMB_PROJECT;
+  private QualityProfileDto sonarWayJava = newQualityProfileDto().setKey("sonar-way-java");
+  private QualityProfileDto sonarWayPhp = newQualityProfileDto().setKey("sonar-way-php");
+  private QualityProfileDto myQualityProfile = newQualityProfileDto().setKey("my-qp");
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  DbClient dbClient = db.getDbClient();
+  DbSession dbSession = db.getSession();
+  QualityProfileDbTester qualityProfileDb = new QualityProfileDbTester(db);
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+
+  UpdateQualityProfilesLastUsedDateStep underTest;
+
+  @Before
+  public void setUp() {
+    underTest = new UpdateQualityProfilesLastUsedDateStep(dbClient, analysisMetadataHolder, treeRootHolder, metricRepository, measureRepository);
+    analysisMetadataHolder.setAnalysisDate(ANALYSIS_DATE);
+    treeRootHolder.setRoot(PROJECT);
+    Metric<String> metric = CoreMetrics.QUALITY_PROFILES;
+    metricRepository.add(new MetricImpl(1, metric.getKey(), metric.getName(), MetricType.STRING));
+
+    qualityProfileDb.insertQualityProfiles(sonarWayJava, sonarWayPhp, myQualityProfile);
+  }
+
+  @Test
+  public void project_without_quality_profiles() {
+    underTest.execute();
+
+    assertQualityProfileIsTheSame(sonarWayJava);
+    assertQualityProfileIsTheSame(sonarWayPhp);
+    assertQualityProfileIsTheSame(myQualityProfile);
+  }
+
+  @Test
+  public void analysis_quality_profiles_are_updated() {
+    measureRepository.addRawMeasure(1, QUALITY_PROFILES_KEY, Measure.newMeasureBuilder().create(
+      toJson(sonarWayJava.getKey(), myQualityProfile.getKey())));
+
+    underTest.execute();
+
+    assertQualityProfileIsTheSame(sonarWayPhp);
+    assertQualityProfileIsUpdated(sonarWayJava);
+    assertQualityProfileIsUpdated(myQualityProfile);
+  }
+
+  @Test
+  public void description() {
+    assertThat(underTest.getDescription()).isEqualTo("Update quality profiles");
+  }
+
+  private void assertQualityProfileIsUpdated(QualityProfileDto qp) {
+    assertThat(selectLastUser(qp.getKey())).withFailMessage("Quality profile '%s' hasn't been updated. Value: %d", qp.getKey(), qp.getLastUsed()).isEqualTo(ANALYSIS_DATE);
+  }
+
+  private void assertQualityProfileIsTheSame(QualityProfileDto qp) {
+    assertThat(selectLastUser(qp.getKey())).isEqualTo(qp.getLastUsed());
+  }
+
+  @CheckForNull
+  private Long selectLastUser(String qualityProfileKey) {
+    return dbClient.qualityProfileDao().selectByKey(dbSession, qualityProfileKey).getLastUsed();
+  }
+
+  private static String toJson(String... keys) {
+    return QPMeasureData.toJson(new QPMeasureData(
+      Arrays.stream(keys)
+        .map(key -> new QualityProfile(key, key, key, new Date()))
+        .collect(Collectors.toList())));
+  }
+}
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1246_add_last_used_column_to_rules_profiles.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1246_add_last_used_column_to_rules_profiles.rb
new file mode 100644 (file)
index 0000000..345bdd5
--- /dev/null
@@ -0,0 +1,28 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+#
+# SonarQube 6.0
+# SONAR-7789
+#
+class AddLastUsedColumnToRulesProfiles < ActiveRecord::Migration
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.AddLastUsedColumnToRulesProfiles')
+  end
+end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1247_populate_last_used_of_rules_profiles.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1247_populate_last_used_of_rules_profiles.rb
new file mode 100644 (file)
index 0000000..b210393
--- /dev/null
@@ -0,0 +1,28 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+#
+# SonarQube 6.0
+# SONAR-7789
+#
+class PopulateLastUsedOfRulesProfiles < ActiveRecord::Migration
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.PopulateLastUsedColumnOfRulesProfiles')
+  end
+end
index b1d54d299e930e0e10ec8b5adecd64380a97afcd..a88742ac85975ec36b66e554af9b43ba0a13d954 100644 (file)
@@ -61,6 +61,10 @@ public class QualityProfileDao implements Dao {
     return dto;
   }
 
+  public List<QualityProfileDto> selectByKeys(DbSession session, List<String> keys) {
+    return executeLargeInputs(keys, mapper(session)::selectByKeys);
+  }
+
   public List<QualityProfileDto> selectAll(DbSession session) {
     return mapper(session).selectAll();
   }
index addb250f35ef5bbcef0c4d789f595e2798733aeb..944b3353c8a977c851870494fbeae06432d65bc6 100644 (file)
@@ -33,6 +33,7 @@ public class QualityProfileDto extends Dto<String> {
   private String language;
   private String parentKee;
   private String rulesUpdatedAt;
+  private Long lastUsed;
   private boolean isDefault;
 
   /**
@@ -112,6 +113,16 @@ public class QualityProfileDto extends Dto<String> {
     return this;
   }
 
+  @CheckForNull
+  public Long getLastUsed() {
+    return lastUsed;
+  }
+
+  public QualityProfileDto setLastUsed(@Nullable Long lastUsed) {
+    this.lastUsed = lastUsed;
+    return this;
+  }
+
   public boolean isDefault() {
     return isDefault;
   }
index b8a6ef79c0918429bfe0673960c4ea9cb7f78523..50a67bc3ddfcee71bbbe81fd267b68295e99cb28 100644 (file)
@@ -52,6 +52,8 @@ public interface QualityProfileMapper {
 
   List<QualityProfileDto> selectByLanguage(String language);
 
+  List<QualityProfileDto> selectByKeys(@Param("keys") List<String> keys);
+
   // INHERITANCE
 
   @CheckForNull
index c5c58f99d32a9a8ae808954a34c456454755805a..9e699e2d1b98f6d5cbfb6378c2a80397cd8768e4 100644 (file)
@@ -30,7 +30,7 @@ import org.sonar.db.MyBatis;
 
 public class DatabaseVersion {
 
-  public static final int LAST_VERSION = 1_245;
+  public static final int LAST_VERSION = 1_247;
 
   /**
    * The minimum supported version which can be upgraded. Lower
@@ -90,8 +90,7 @@ public class DatabaseVersion {
     "user_roles",
     "user_tokens",
     "widgets",
-    "widget_properties"
-    );
+    "widget_properties");
   private MyBatis mybatis;
 
   public DatabaseVersion(MyBatis mybatis) {
index 4f4d7cfef7bc217c35c2d199d905e1a877a19453..df052f0228d4f05b4a03620dc694ed5730238dfe 100644 (file)
@@ -88,6 +88,7 @@ import org.sonar.db.version.v60.AddAnalysisUuidColumnToDuplicationsIndex;
 import org.sonar.db.version.v60.AddComponentUuidColumnToDuplicationsIndex;
 import org.sonar.db.version.v60.AddComponentUuidColumnToMeasures;
 import org.sonar.db.version.v60.AddComponentUuidColumnsToSnapshots;
+import org.sonar.db.version.v60.AddLastUsedColumnToRulesProfiles;
 import org.sonar.db.version.v60.AddUuidColumnToSnapshots;
 import org.sonar.db.version.v60.AddUuidColumnsToProjects;
 import org.sonar.db.version.v60.AddUuidColumnsToResourceIndex;
@@ -117,6 +118,7 @@ import org.sonar.db.version.v60.PopulateAnalysisUuidOfDuplicationsIndex;
 import org.sonar.db.version.v60.PopulateComponentUuidColumnsOfSnapshots;
 import org.sonar.db.version.v60.PopulateComponentUuidOfDuplicationsIndex;
 import org.sonar.db.version.v60.PopulateComponentUuidOfMeasures;
+import org.sonar.db.version.v60.PopulateLastUsedColumnOfRulesProfiles;
 import org.sonar.db.version.v60.PopulateUuidColumnOnSnapshots;
 import org.sonar.db.version.v60.PopulateUuidColumnsOfProjects;
 import org.sonar.db.version.v60.PopulateUuidColumnsOfResourceIndex;
@@ -227,6 +229,8 @@ public class MigrationStepModule extends Module {
       CleanOrphanRowsInProjects.class,
       MakeUuidColumnsNotNullOnProjects.class,
       DropIdColumnsFromProjects.class,
+      AddLastUsedColumnToRulesProfiles.class,
+      PopulateLastUsedColumnOfRulesProfiles.class,
 
       // SNAPSHOTS.UUID
       AddUuidColumnToSnapshots.class,
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfiles.java b/sonar-db/src/main/java/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfiles.java
new file mode 100644 (file)
index 0000000..bcdcfe4
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.version.v60;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+import org.sonar.db.version.DdlChange;
+
+import static org.sonar.db.version.BigDecimalColumnDef.newBigDecimalColumnDefBuilder;
+
+public class AddLastUsedColumnToRulesProfiles extends DdlChange {
+
+  private static final String TABLE_QUALITY_PROFILES = "rules_profiles";
+
+  public AddLastUsedColumnToRulesProfiles(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_QUALITY_PROFILES)
+      .addColumn(newBigDecimalColumnDefBuilder().setColumnName("last_used").setIsNullable(true).build())
+      .build());
+  }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfiles.java b/sonar-db/src/main/java/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfiles.java
new file mode 100644 (file)
index 0000000..66f98d2
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.version.v60;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+
+public class PopulateLastUsedColumnOfRulesProfiles extends BaseDataChange {
+
+  private static final Pattern PATTERN_QP_KEY = Pattern.compile("\"key\"\\s*:\\s*\"(.*?)\"");
+
+  public PopulateLastUsedColumnOfRulesProfiles(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    Map<String, Long> lastAnalysisDatesByQualityProfileKey = buildQualityProfilesMap(context);
+    if (lastAnalysisDatesByQualityProfileKey.isEmpty()) {
+      return;
+    }
+
+    populateLastUsedColumn(context, lastAnalysisDatesByQualityProfileKey);
+  }
+
+  private static Map<String, Long> buildQualityProfilesMap(Context context) throws SQLException {
+    Map<String, Long> lastAnalysisDatesByQPKeys = new HashMap<>();
+
+    context.prepareSelect("select s.created_at, pm.text_value " +
+      "from project_measures pm " +
+      "  inner join snapshots s on pm.snapshot_id = s.id " +
+      "  inner join metrics m on pm.metric_id=m.id " +
+      "where s.islast=? " +
+      "  and m.name='quality_profiles' " +
+      "order by s.created_at ")
+      .setBoolean(1, true)
+      .scroll(row -> {
+        long analysisDate = row.getLong(1);
+        String json = row.getString(2);
+        Matcher matcher = PATTERN_QP_KEY.matcher(json);
+        while (matcher.find()) {
+          lastAnalysisDatesByQPKeys.put(matcher.group(1), analysisDate);
+        }
+      });
+    return lastAnalysisDatesByQPKeys;
+  }
+
+  private static void populateLastUsedColumn(Context context, Map<String, Long> lastAnalysisDatesByQualityProfileKey) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select("select id, kee from rules_profiles where last_used is null");
+    massUpdate.update("update rules_profiles set last_used=? where id=?");
+    massUpdate.rowPluralName("rules_profiles");
+    massUpdate.execute((row, update) -> handle(lastAnalysisDatesByQualityProfileKey, row, update));
+  }
+
+  private static boolean handle(Map<String, Long> lastAnalysisDatesByQualityProfileKey, Select.Row row, SqlStatement update) throws SQLException {
+    int qualityProfileId = row.getInt(1);
+    String qualityProfileKey = row.getString(2);
+
+    update.setLong(1, lastAnalysisDatesByQualityProfileKey.get(qualityProfileKey));
+    update.setInt(2, qualityProfileId);
+
+    return true;
+  }
+}
index edc2563f8e201dac88b009c79d0965f67adc803e..e33f92db1d237f906d76c5ba9555b7aa23965c9d 100644 (file)
     p.is_default as isDefault,
     p.created_at as createdAt,
     p.updated_at as updatedAt,
-    p.rules_updated_at as rulesUpdatedAt
+    p.rules_updated_at as rulesUpdatedAt,
+    p.last_used as lastUsed
   </sql>
 
   <insert id="insert" parameterType="QualityProfile" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
-    INSERT INTO rules_profiles (kee, parent_kee, name, language, is_default, created_at, updated_at, rules_updated_at)
-    VALUES (#{kee}, #{parentKee}, #{name}, #{language}, #{isDefault}, #{createdAt}, #{updatedAt}, #{rulesUpdatedAt,})
+    INSERT INTO rules_profiles (kee, parent_kee, name, language, is_default, created_at, updated_at, rules_updated_at, last_used)
+    VALUES (#{kee, jdbcType=VARCHAR}, #{parentKee,jdbcType=VARCHAR}, #{name, jdbcType=VARCHAR}, #{language, jdbcType=VARCHAR}, #{isDefault, jdbcType=BOOLEAN},
+    #{createdAt, jdbcType=TIMESTAMP}, #{updatedAt, jdbcType=TIMESTAMP}, #{rulesUpdatedAt, jdbcType=VARCHAR}, #{lastUsed, jdbcType=BIGINT})
   </insert>
 
   <update id="update" parameterType="QualityProfile">
     UPDATE rules_profiles SET
-    name=#{name},
-    language=#{language},
-    is_default=#{isDefault},
-    parent_kee=#{parentKee},
-    updated_at=#{updatedAt},
-    rules_updated_at=#{rulesUpdatedAt}
+    name=#{name, jdbcType=VARCHAR},
+    language=#{language, jdbcType=VARCHAR},
+    is_default=#{isDefault, jdbcType=BOOLEAN},
+    parent_kee=#{parentKee, jdbcType=VARCHAR},
+    updated_at=#{updatedAt, jdbcType=TIMESTAMP},
+    rules_updated_at=#{rulesUpdatedAt, jdbcType=VARCHAR},
+    last_used=#{lastUsed, jdbcType=BIGINT}
     WHERE id=#{id}
   </update>
 
     WHERE p.kee=#{id}
   </select>
 
+  <select id="selectByKeys" parameterType="string" resultType="QualityProfile">
+    SELECT
+    <include refid="profilesColumns"/>
+    FROM rules_profiles p
+    WHERE p.kee in
+    <foreach collection="keys" open="(" close=")" item="key" separator=",">
+      #{key}
+    </foreach>
+  </select>
+
   <select id="selectByLanguage" parameterType="String" resultType="QualityProfile">
     SELECT
     <include refid="profilesColumns"/>
index 6f8bca251cf1e6bef4706659e351885c10059b2b..9c370a1c52df6014f3f2958a3d8026dd359cbd66 100644 (file)
@@ -452,6 +452,8 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1242');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1243');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1244');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1245');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1246');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1247');
 
 INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482');
 ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
index aa11e3b2babafbaaabd0893cb3d5bb72dc4d65a9..6ae2e93429fefe79fea0540215e024276dd22ae5 100644 (file)
@@ -21,7 +21,8 @@ CREATE TABLE "RULES_PROFILES" (
   "RULES_UPDATED_AT" VARCHAR(100),
   "IS_DEFAULT" BOOLEAN NOT NULL DEFAULT FALSE,
   "CREATED_AT" TIMESTAMP,
-  "UPDATED_AT" TIMESTAMP
+  "UPDATED_AT" TIMESTAMP,
+  "LAST_USED" BIGINT
 );
 
 CREATE TABLE "PROJECT_QPROFILES" (
index d47a6e19467850db6a41ee43320d153a8547ce92..d0f52c3643b0bf75da6eb9d470b010a51f193b1e 100644 (file)
@@ -26,14 +26,18 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.UtcDateUtils;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 
 import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-
+import static org.sonar.db.qualityprofile.QualityProfileTesting.newQualityProfileDto;
 
 public class QualityProfileDaoTest {
 
@@ -41,8 +45,11 @@ public class QualityProfileDaoTest {
 
   @Rule
   public DbTester dbTester = DbTester.create(system);
+  DbClient dbClient = dbTester.getDbClient();
+  DbSession dbSession = dbTester.getSession();
+  QualityProfileDbTester qualityProfileDb = new QualityProfileDbTester(dbTester);
 
-  QualityProfileDao dao = dbTester.getDbClient().qualityProfileDao();
+  QualityProfileDao underTest = dbTester.getDbClient().qualityProfileDao();
 
   @Before
   public void createDao() {
@@ -57,9 +64,9 @@ public class QualityProfileDaoTest {
       .setName("ABCDE")
       .setLanguage("xoo");
 
-    dao.insert(dto);
+    underTest.insert(dto);
 
-    dbTester.assertDbUnit(getClass(), "insert-result.xml", new String[]{"created_at", "updated_at", "rules_updated_at"}, "rules_profiles");
+    dbTester.assertDbUnit(getClass(), "insert-result.xml", new String[] {"created_at", "updated_at", "rules_updated_at"}, "rules_profiles");
   }
 
   @Test
@@ -73,16 +80,16 @@ public class QualityProfileDaoTest {
       .setParentKee("fghij")
       .setDefault(false);
 
-    dao.update(dto);
+    underTest.update(dto);
 
-    dbTester.assertDbUnit(getClass(), "update-result.xml", new String[]{"created_at", "updated_at", "rules_updated_at"}, "rules_profiles");
+    dbTester.assertDbUnit(getClass(), "update-result.xml", new String[] {"created_at", "updated_at", "rules_updated_at"}, "rules_profiles");
   }
 
   @Test
   public void delete() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
 
-    dao.delete(1);
+    underTest.delete(1);
 
     dbTester.assertDbUnit(getClass(), "delete-result.xml", "rules_profiles");
   }
@@ -91,7 +98,7 @@ public class QualityProfileDaoTest {
   public void find_all() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
 
-    List<QualityProfileDto> dtos = dao.selectAll(dbTester.getSession());
+    List<QualityProfileDto> dtos = underTest.selectAll(dbTester.getSession());
 
     assertThat(dtos).hasSize(2);
 
@@ -112,7 +119,7 @@ public class QualityProfileDaoTest {
   public void find_all_is_sorted_by_profile_name() {
     dbTester.prepareDbUnit(getClass(), "select_all_is_sorted_by_profile_name.xml");
 
-    List<QualityProfileDto> dtos = dao.selectAll();
+    List<QualityProfileDto> dtos = underTest.selectAll();
 
     assertThat(dtos).hasSize(3);
     assertThat(dtos.get(0).getName()).isEqualTo("First");
@@ -124,44 +131,44 @@ public class QualityProfileDaoTest {
   public void get_default_profile() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
 
-    QualityProfileDto java = dao.selectDefaultProfile("java");
+    QualityProfileDto java = underTest.selectDefaultProfile("java");
     assertThat(java).isNotNull();
     assertThat(java.getKey()).isEqualTo("java_sonar_way");
 
-    assertThat(dao.selectDefaultProfile("js")).isNull();
+    assertThat(underTest.selectDefaultProfile("js")).isNull();
   }
 
   @Test
   public void get_default_profiles() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
 
-    List<QualityProfileDto> java = dao.selectDefaultProfiles(dbTester.getSession(), singletonList("java"));
+    List<QualityProfileDto> java = underTest.selectDefaultProfiles(dbTester.getSession(), singletonList("java"));
     assertThat(java).extracting("key").containsOnly("java_sonar_way");
 
-    assertThat(dao.selectDefaultProfiles(dbTester.getSession(), singletonList("js"))).isEmpty();
-    assertThat(dao.selectDefaultProfiles(dbTester.getSession(), of("java", "js"))).extracting("key").containsOnly("java_sonar_way");
-    assertThat(dao.selectDefaultProfiles(dbTester.getSession(), of("js", "java"))).extracting("key").containsOnly("java_sonar_way");
+    assertThat(underTest.selectDefaultProfiles(dbTester.getSession(), singletonList("js"))).isEmpty();
+    assertThat(underTest.selectDefaultProfiles(dbTester.getSession(), of("java", "js"))).extracting("key").containsOnly("java_sonar_way");
+    assertThat(underTest.selectDefaultProfiles(dbTester.getSession(), of("js", "java"))).extracting("key").containsOnly("java_sonar_way");
   }
 
   @Test
   public void get_by_name_and_language() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
 
-    QualityProfileDto dto = dao.selectByNameAndLanguage("Sonar Way", "java", dbTester.getSession());
+    QualityProfileDto dto = underTest.selectByNameAndLanguage("Sonar Way", "java", dbTester.getSession());
     assertThat(dto.getId()).isEqualTo(1);
     assertThat(dto.getName()).isEqualTo("Sonar Way");
     assertThat(dto.getLanguage()).isEqualTo("java");
     assertThat(dto.getParentKee()).isNull();
 
-    assertThat(dao.selectByNameAndLanguage("Sonar Way", "java", dbTester.getSession())).isNotNull();
-    assertThat(dao.selectByNameAndLanguage("Sonar Way", "unknown", dbTester.getSession())).isNull();
+    assertThat(underTest.selectByNameAndLanguage("Sonar Way", "java", dbTester.getSession())).isNotNull();
+    assertThat(underTest.selectByNameAndLanguage("Sonar Way", "unknown", dbTester.getSession())).isNull();
   }
 
   @Test
   public void get_by_name_and_languages() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
 
-    List<QualityProfileDto> dtos = dao.selectByNameAndLanguages("Sonar Way", singletonList("java"), dbTester.getSession());
+    List<QualityProfileDto> dtos = underTest.selectByNameAndLanguages("Sonar Way", singletonList("java"), dbTester.getSession());
     assertThat(dtos).hasSize(1);
     QualityProfileDto dto = dtos.iterator().next();
     assertThat(dto.getId()).isEqualTo(1);
@@ -169,15 +176,15 @@ public class QualityProfileDaoTest {
     assertThat(dto.getLanguage()).isEqualTo("java");
     assertThat(dto.getParentKee()).isNull();
 
-    assertThat(dao.selectByNameAndLanguages("Sonar Way", singletonList("unknown"), dbTester.getSession())).isEmpty();
-    assertThat(dao.selectByNameAndLanguages("Sonar Way", of("java", "unknown"), dbTester.getSession())).extracting("id").containsOnly(1);
+    assertThat(underTest.selectByNameAndLanguages("Sonar Way", singletonList("unknown"), dbTester.getSession())).isEmpty();
+    assertThat(underTest.selectByNameAndLanguages("Sonar Way", of("java", "unknown"), dbTester.getSession())).extracting("id").containsOnly(1);
   }
 
   @Test
   public void find_by_language() {
     dbTester.prepareDbUnit(getClass(), "select_by_language.xml");
 
-    List<QualityProfileDto> result = dao.selectByLanguage("java");
+    List<QualityProfileDto> result = underTest.selectByLanguage("java");
     assertThat(result).hasSize(2);
     assertThat(result.get(0).getName()).isEqualTo("Sonar Way 1");
     assertThat(result.get(1).getName()).isEqualTo("Sonar Way 2");
@@ -187,20 +194,20 @@ public class QualityProfileDaoTest {
   public void get_by_id() {
     dbTester.prepareDbUnit(getClass(), "shared.xml");
 
-    QualityProfileDto dto = dao.selectById(1);
+    QualityProfileDto dto = underTest.selectById(1);
     assertThat(dto.getId()).isEqualTo(1);
     assertThat(dto.getName()).isEqualTo("Sonar Way");
     assertThat(dto.getLanguage()).isEqualTo("java");
     assertThat(dto.getParentKee()).isNull();
 
-    assertThat(dao.selectById(555)).isNull();
+    assertThat(underTest.selectById(555)).isNull();
   }
 
   @Test
   public void get_parent_by_id() {
     dbTester.prepareDbUnit(getClass(), "inheritance.xml");
 
-    QualityProfileDto dto = dao.selectParentById(1);
+    QualityProfileDto dto = underTest.selectParentById(1);
     assertThat(dto.getId()).isEqualTo(3);
   }
 
@@ -208,7 +215,7 @@ public class QualityProfileDaoTest {
   public void find_children() {
     dbTester.prepareDbUnit(getClass(), "inheritance.xml");
 
-    List<QualityProfileDto> dtos = dao.selectChildren(dbTester.getSession(), "java_parent");
+    List<QualityProfileDto> dtos = underTest.selectChildren(dbTester.getSession(), "java_parent");
 
     assertThat(dtos).hasSize(2);
 
@@ -229,21 +236,21 @@ public class QualityProfileDaoTest {
   public void select_projects() {
     dbTester.prepareDbUnit(getClass(), "projects.xml");
 
-    assertThat(dao.selectProjects("Sonar Way", "java")).hasSize(2);
+    assertThat(underTest.selectProjects("Sonar Way", "java")).hasSize(2);
   }
 
   @Test
   public void count_projects() {
     dbTester.prepareDbUnit(getClass(), "projects.xml");
 
-    assertThat(dao.countProjects("Sonar Way", "java")).isEqualTo(2);
+    assertThat(underTest.countProjects("Sonar Way", "java")).isEqualTo(2);
   }
 
   @Test
   public void count_projects_by_profile() {
     dbTester.prepareDbUnit(getClass(), "projects.xml");
 
-    assertThat(dao.countProjectsByProfileKey()).containsOnly(
+    assertThat(underTest.countProjectsByProfileKey()).containsOnly(
       MapEntry.entry("java_sonar_way", 2L),
       MapEntry.entry("js_sonar_way", 2L));
   }
@@ -252,7 +259,7 @@ public class QualityProfileDaoTest {
   public void select_by_project_id_and_language() {
     dbTester.prepareDbUnit(getClass(), "projects.xml");
 
-    QualityProfileDto dto = dao.selectByProjectAndLanguage(1L, "java");
+    QualityProfileDto dto = underTest.selectByProjectAndLanguage(1L, "java");
     assertThat(dto.getId()).isEqualTo(1);
   }
 
@@ -260,22 +267,35 @@ public class QualityProfileDaoTest {
   public void select_by_project_key_and_language() {
     dbTester.prepareDbUnit(getClass(), "projects.xml");
 
-    QualityProfileDto dto = dao.selectByProjectAndLanguage(dbTester.getSession(), "org.codehaus.sonar:sonar", "java");
+    QualityProfileDto dto = underTest.selectByProjectAndLanguage(dbTester.getSession(), "org.codehaus.sonar:sonar", "java");
     assertThat(dto.getId()).isEqualTo(1);
 
-    assertThat(dao.selectByProjectAndLanguage(dbTester.getSession(), "org.codehaus.sonar:sonar", "unkown")).isNull();
-    assertThat(dao.selectByProjectAndLanguage(dbTester.getSession(), "unknown", "java")).isNull();
+    assertThat(underTest.selectByProjectAndLanguage(dbTester.getSession(), "org.codehaus.sonar:sonar", "unkown")).isNull();
+    assertThat(underTest.selectByProjectAndLanguage(dbTester.getSession(), "unknown", "java")).isNull();
   }
 
   @Test
   public void select_by_project_key_and_languages() {
     dbTester.prepareDbUnit(getClass(), "projects.xml");
 
-    List<QualityProfileDto> dto = dao.selectByProjectAndLanguages(dbTester.getSession(), "org.codehaus.sonar:sonar", singletonList("java"));
+    List<QualityProfileDto> dto = underTest.selectByProjectAndLanguages(dbTester.getSession(), "org.codehaus.sonar:sonar", singletonList("java"));
     assertThat(dto).extracting("id").containsOnly(1);
 
-    assertThat(dao.selectByProjectAndLanguages(dbTester.getSession(), "org.codehaus.sonar:sonar", singletonList("unkown"))).isEmpty();
-    assertThat(dao.selectByProjectAndLanguages(dbTester.getSession(), "org.codehaus.sonar:sonar", of("java", "unkown"))).extracting("id").containsOnly(1);
-    assertThat(dao.selectByProjectAndLanguages(dbTester.getSession(), "unknown", singletonList("java"))).isEmpty();
+    assertThat(underTest.selectByProjectAndLanguages(dbTester.getSession(), "org.codehaus.sonar:sonar", singletonList("unkown"))).isEmpty();
+    assertThat(underTest.selectByProjectAndLanguages(dbTester.getSession(), "org.codehaus.sonar:sonar", of("java", "unkown"))).extracting("id").containsOnly(1);
+    assertThat(underTest.selectByProjectAndLanguages(dbTester.getSession(), "unknown", singletonList("java"))).isEmpty();
+  }
+
+  @Test
+  public void selectByKeys() {
+    qualityProfileDb.insertQualityProfiles(newQualityProfileDto().setKey("qp-key-1"), newQualityProfileDto().setKee("qp-key-2"), newQualityProfileDto().setKee("qp-key-3"));
+
+    assertThat(underTest.selectOrFailByKey(dbSession, "qp-key-1")).isNotNull();
+    assertThat(underTest.selectByKey(dbSession, "qp-key-1")).isNotNull();
+    assertThat(underTest.selectByKey(dbSession, "qp-key-42")).isNull();
+    assertThat(underTest.selectByKeys(dbSession, newArrayList("qp-key-1", "qp-key-3", "qp-key-42")))
+      .hasSize(2)
+      .extracting(QualityProfileDto::getKey).containsOnlyOnce("qp-key-1", "qp-key-3");
+    assertThat(underTest.selectByKeys(dbSession, emptyList())).isEmpty();
   }
 }
index 0dd2ed189dd9bd63562ff3f9c929f6b9f5a38266..582b8f546e53aa2b0fdad48d3b15e4a7ec396513 100644 (file)
@@ -35,6 +35,7 @@ public class QualityProfileDbTester {
 
   public void insertQualityProfiles(QualityProfileDto qualityProfile, QualityProfileDto... qualityProfiles) {
     dbClient.qualityProfileDao().insert(dbSession, qualityProfile, qualityProfiles);
+    dbSession.commit();
   }
 
   public void insertProjectWithQualityProfileAssociations(ComponentDto project, QualityProfileDto... qualityProfiles) {
@@ -42,6 +43,8 @@ public class QualityProfileDbTester {
     for (QualityProfileDto qualityProfile : qualityProfiles) {
       dbClient.qualityProfileDao().insertProjectProfileAssociation(project.uuid(), qualityProfile.getKey(), dbSession);
     }
+    
+    dbSession.commit();
   }
 
 }
diff --git a/sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileTesting.java b/sonar-db/src/test/java/org/sonar/db/qualityprofile/QualityProfileTesting.java
new file mode 100644 (file)
index 0000000..836b5be
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.qualityprofile;
+
+import java.util.Date;
+import org.sonar.core.util.Uuids;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.apache.commons.lang.math.RandomUtils.nextLong;
+
+public class QualityProfileTesting {
+  public static QualityProfileDto newQualityProfileDto() {
+    String uuid = Uuids.createFast();
+    QualityProfileDto dto = QualityProfileDto.createFor(uuid)
+      .setName(uuid)
+      .setLanguage(randomAlphanumeric(20))
+      .setLastUsed(nextLong());
+    dto.setCreatedAt(new Date())
+      .setUpdatedAt(new Date());
+    return dto;
+  }
+}
index da5b4c20ed21358f6b7bb161db48e7563ef661f2..aef48b2d99b4d840b260e9b571c6a59d8546fdcf 100644 (file)
@@ -29,6 +29,6 @@ public class MigrationStepModuleTest {
   public void verify_count_of_added_MigrationStep_types() {
     ComponentContainer container = new ComponentContainer();
     new MigrationStepModule().configure(container);
-    assertThat(container.size()).isEqualTo(102);
+    assertThat(container.size()).isEqualTo(104);
   }
 }
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfilesTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfilesTest.java
new file mode 100644 (file)
index 0000000..1dc10d7
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.version.v60;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+public class AddLastUsedColumnToRulesProfilesTest {
+
+  @Rule
+  public DbTester db = DbTester.createForSchema(System2.INSTANCE, AddLastUsedColumnToRulesProfilesTest.class, "rules_profiles.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddLastUsedColumnToRulesProfiles underTest = new AddLastUsedColumnToRulesProfiles(db.database());
+
+  @Test
+  public void migration_adds_column_to_empty_table() throws SQLException {
+    underTest.execute();
+
+    verifyAddedColumns();
+  }
+
+  @Test
+  public void migration_adds_column_to_populated_table() throws SQLException {
+    for (int i = 0; i < 9; i++) {
+      db.executeInsert(
+        "rules_profiles",
+        "name", "NAME_" + i,
+        "language", "java",
+        "kee", "" + i,
+        "rules_updated_at", "2016-06-21");
+    }
+    db.commit();
+
+    underTest.execute();
+
+    verifyAddedColumns();
+  }
+
+  @Test
+  public void migration_is_not_reentrant() throws SQLException {
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Fail to execute ");
+    underTest.execute();
+  }
+
+  private void verifyAddedColumns() {
+    db.assertColumnDefinition("rules_profiles", "last_used", Types.BIGINT, null, true);
+  }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfilesTest.java b/sonar-db/src/test/java/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfilesTest.java
new file mode 100644 (file)
index 0000000..6c178ca
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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.version.v60;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static java.lang.String.valueOf;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PopulateLastUsedColumnOfRulesProfilesTest {
+  private static final String QUALITY_PROFILES_TABLE = "rules_profiles";
+  private static final String METRICS_TABLE = "metrics";
+  private static final String MEASURES_TABLE = "project_measures";
+  private static final String SNAPSHOTS_TABLE = "snapshots";
+  private static final String METRIC_ID = "1";
+
+  @Rule
+  public DbTester db = DbTester.createForSchema(System2.INSTANCE, PopulateLastUsedColumnOfRulesProfilesTest.class, "rules_profiles.sql");
+
+  PopulateLastUsedColumnOfRulesProfiles underTest = new PopulateLastUsedColumnOfRulesProfiles(db.database());
+
+  @Test
+  public void migration_has_no_effect_on_empty_tables() throws SQLException {
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable(QUALITY_PROFILES_TABLE)).isEqualTo(0);
+    assertThat(db.countRowsOfTable(METRICS_TABLE)).isEqualTo(0);
+    assertThat(db.countRowsOfTable(MEASURES_TABLE)).isEqualTo(0);
+    assertThat(db.countRowsOfTable(SNAPSHOTS_TABLE)).isEqualTo(0);
+  }
+
+  @Test
+  public void migration_update_quality_profiles_last_used() throws SQLException {
+    insertQualityProfilesMetric();
+    insertQualityProfile(1, "first-quality-profile");
+    insertQualityProfile(2, "second-quality-profile");
+    insertQualityProfile(3, "third-quality-profile");
+    insertQualityProfile(4, "fourth-quality-profile");
+    insertMeasure(1, "first-quality-profile", "second-quality-profile");
+    insertMeasure(2, "second-quality-profile", "third-quality-profile");
+
+    underTest.execute();
+
+    assertLastUsedForQP("first-quality-profile", 1);
+    assertLastUsedForQP("second-quality-profile", 2);
+    assertLastUsedForQP("third-quality-profile", 2);
+    assertNoLastUsedForQP("fourth-quality-profile");
+  }
+
+  @Test
+  public void migration_is_reentrant() throws SQLException {
+    insertQualityProfilesMetric();
+    insertQualityProfile(1, "first-quality-profile");
+    insertMeasure(1, "first-quality-profile");
+
+    underTest.execute();
+    assertLastUsedForQP("first-quality-profile", 1);
+
+    underTest.execute();
+    assertLastUsedForQP("first-quality-profile", 1);
+  }
+
+  private void assertLastUsedForQP(String qualityProfileKey, long expectedLastUsed) {
+    assertThat(selectLastUser(qualityProfileKey)).isEqualTo(expectedLastUsed);
+  }
+
+  private void assertNoLastUsedForQP(String qualityProfileKey) {
+    assertThat(selectLastUser(qualityProfileKey)).isNull();
+  }
+
+  @CheckForNull
+  private Long selectLastUser(String qualityProfileKey) {
+    return (Long) db.selectFirst(String.format("select last_used as \"lastUsed\" from rules_profiles where kee ='%s'", qualityProfileKey)).get("lastUsed");
+  }
+
+  private void insertQualityProfile(long id, String key) {
+    db.executeInsert(QUALITY_PROFILES_TABLE,
+      "id", valueOf(id),
+      "name", key,
+      "kee", key);
+  }
+
+  private void insertMeasure(long id, String... keys) {
+    db.executeInsert(
+      SNAPSHOTS_TABLE,
+      "id", valueOf(id),
+      "uuid", valueOf(id),
+      "component_uuid", valueOf(id),
+      "root_component_uuid", valueOf(id),
+      "islast", "TRUE",
+      "created_at", valueOf(id));
+
+    db.executeInsert(
+      MEASURES_TABLE,
+      "id", valueOf(id),
+      "snapshot_id", valueOf(id),
+      "metric_id", METRIC_ID,
+      "component_uuid", valueOf(id),
+      "text_value", toJson(keys));
+  }
+
+  private void insertQualityProfilesMetric() {
+    db.executeInsert(METRICS_TABLE,
+      "id", METRIC_ID,
+      "name", "quality_profiles");
+  }
+
+  private static String toJson(String... keys) {
+    return Arrays.stream(keys).map(key -> "\"key\" : \"" + key + "\"").collect(Collectors.joining(", ", "{", "}"));
+  }
+}
index fac2543235e787ecf795def20b1829e1da3c43d5..a503c6b3950493c26f3b81dfc22d77a7c9a1cb8a 100644 (file)
@@ -1,6 +1,6 @@
 <dataset>
 
   <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="123456789"/>
 
 </dataset>
index 8a9e9ce1adaba0ac71d5b867df21c42e665fc26d..9090f8728dc989c93b657034d3c7f8db40e14712 100644 (file)
@@ -1,13 +1,13 @@
 <dataset>
 
   <rules_profiles id="1" name="Sonar Way" language="java" parent_kee="[null]" kee="java_sonar_way" is_default="[true]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="[null]"/>
 
   <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="123456789"/>
 
   <rules_profiles id="3" name="ABCDE" language="xoo" parent_kee="[null]" kee="abcde" is_default="[false]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="[null]"/>
 
 
 </dataset>
index bf8e0113b249bd62a5f6840f57c82abc42c5caed..92509d96554cfdb298e3c05097211f27b3e779d6 100644 (file)
@@ -1,9 +1,9 @@
 <dataset>
 
   <rules_profiles id="1" name="Sonar Way" language="java" parent_kee="[null]" kee="java_sonar_way" is_default="[true]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="[null]"/>
 
   <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="123456789"/>
 
 </dataset>
index 3d54167f58dcb2e7d82d47525fc501b7a78819e4..952372c6a12759f667a2c516e044f6865c4ee530 100644 (file)
@@ -1,10 +1,10 @@
 <dataset>
 
   <rules_profiles id="1" name="New Name" language="js" parent_kee="fghij" kee="java_sonar_way" is_default="[false]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="[null]"/>
 
   <rules_profiles id="2" name="Sonar Way" language="js" parent_kee="[null]" kee="js_sonar_way" is_default="[false]"
-                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]"/>
+                  rules_updated_at="[null]" created_at="[null]" updated_at="[null]" last_used="123456789"/>
 
 
 </dataset>
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfilesTest/rules_profiles.sql b/sonar-db/src/test/resources/org/sonar/db/version/v60/AddLastUsedColumnToRulesProfilesTest/rules_profiles.sql
new file mode 100644 (file)
index 0000000..cddbb20
--- /dev/null
@@ -0,0 +1,13 @@
+CREATE TABLE "RULES_PROFILES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "NAME" VARCHAR(100) NOT NULL,
+  "LANGUAGE" VARCHAR(20),
+  "KEE" VARCHAR(255) NOT NULL,
+  "PARENT_KEE" VARCHAR(255),
+  "RULES_UPDATED_AT" VARCHAR(100),
+  "IS_DEFAULT" BOOLEAN NOT NULL DEFAULT FALSE,
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP
+);
+
+CREATE UNIQUE INDEX "UNIQ_QPROF_KEY" ON "RULES_PROFILES" ("KEE");
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfilesTest/rules_profiles.sql b/sonar-db/src/test/resources/org/sonar/db/version/v60/PopulateLastUsedColumnOfRulesProfilesTest/rules_profiles.sql
new file mode 100644 (file)
index 0000000..8f6183f
--- /dev/null
@@ -0,0 +1,84 @@
+CREATE TABLE "RULES_PROFILES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "NAME" VARCHAR(100) NOT NULL,
+  "LANGUAGE" VARCHAR(20),
+  "KEE" VARCHAR(255) NOT NULL,
+  "PARENT_KEE" VARCHAR(255),
+  "RULES_UPDATED_AT" VARCHAR(100),
+  "IS_DEFAULT" BOOLEAN NOT NULL DEFAULT FALSE,
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP,
+  "LAST_USED" BIGINT
+);
+
+CREATE TABLE "PROJECT_MEASURES" (
+  "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "VALUE" DOUBLE,
+  "METRIC_ID" INTEGER NOT NULL,
+  "COMPONENT_UUID" VARCHAR(50) NOT NULL,
+  "SNAPSHOT_ID" INTEGER,
+  "TEXT_VALUE" VARCHAR(4000),
+  "ALERT_STATUS" VARCHAR(5),
+  "ALERT_TEXT" VARCHAR(4000),
+  "DESCRIPTION" VARCHAR(4000),
+  "PERSON_ID" INTEGER,
+  "VARIATION_VALUE_1" DOUBLE,
+  "VARIATION_VALUE_2" DOUBLE,
+  "VARIATION_VALUE_3" DOUBLE,
+  "VARIATION_VALUE_4" DOUBLE,
+  "VARIATION_VALUE_5" DOUBLE,
+  "MEASURE_DATA" BINARY(167772150)
+);
+
+CREATE TABLE "SNAPSHOTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(50) NOT NULL,
+  "CREATED_AT" BIGINT,
+  "BUILD_DATE" BIGINT,
+  "COMPONENT_UUID" VARCHAR(50) NOT NULL,
+  "PARENT_SNAPSHOT_ID" INTEGER,
+  "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U',
+  "PURGE_STATUS" INTEGER,
+  "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "ROOT_SNAPSHOT_ID" INTEGER,
+  "VERSION" VARCHAR(500),
+  "PATH" VARCHAR(500),
+  "DEPTH" INTEGER,
+  "ROOT_COMPONENT_UUID" VARCHAR(50) NOT NULL,
+  "PERIOD1_MODE" VARCHAR(100),
+  "PERIOD1_PARAM" VARCHAR(100),
+  "PERIOD1_DATE" BIGINT,
+  "PERIOD2_MODE" VARCHAR(100),
+  "PERIOD2_PARAM" VARCHAR(100),
+  "PERIOD2_DATE" BIGINT,
+  "PERIOD3_MODE" VARCHAR(100),
+  "PERIOD3_PARAM" VARCHAR(100),
+  "PERIOD3_DATE" BIGINT,
+  "PERIOD4_MODE" VARCHAR(100),
+  "PERIOD4_PARAM" VARCHAR(100),
+  "PERIOD4_DATE" BIGINT,
+  "PERIOD5_MODE" VARCHAR(100),
+  "PERIOD5_PARAM" VARCHAR(100),
+  "PERIOD5_DATE" BIGINT
+);
+
+CREATE TABLE "METRICS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "NAME" VARCHAR(64) NOT NULL,
+  "DESCRIPTION" VARCHAR(255),
+  "DIRECTION" INTEGER NOT NULL DEFAULT 0,
+  "DOMAIN" VARCHAR(64),
+  "SHORT_NAME" VARCHAR(64),
+  "QUALITATIVE" BOOLEAN NOT NULL DEFAULT FALSE,
+  "VAL_TYPE" VARCHAR(8),
+  "USER_MANAGED" BOOLEAN DEFAULT FALSE,
+  "ENABLED" BOOLEAN DEFAULT TRUE,
+  "WORST_VALUE" DOUBLE,
+  "BEST_VALUE" DOUBLE,
+  "OPTIMIZED_BEST_VALUE" BOOLEAN,
+  "HIDDEN" BOOLEAN,
+  "DELETE_HISTORICAL_DATA" BOOLEAN,
+  "DECIMAL_SCALE" INTEGER
+);