]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4690 SONAR-4691 speed-up db migration of violations
authorSimon Brandhof <simon.brandhof@gmail.com>
Tue, 24 Sep 2013 18:04:22 +0000 (20:04 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Tue, 24 Sep 2013 18:04:22 +0000 (20:04 +0200)
33 files changed:
sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java
sonar-server/src/main/java/org/sonar/server/db/DatabaseMigration.java [deleted file]
sonar-server/src/main/java/org/sonar/server/db/DatabaseMigrator.java [deleted file]
sonar-server/src/main/java/org/sonar/server/db/migrations/ConvertViolationsToIssues.java [deleted file]
sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrations.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/violation/Progress.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/violation/Referentials.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/violation/SqlUtil.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationConverter.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationConverters.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationMigration.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/db/migrations/violation/package-info.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/webapp/WEB-INF/config/environment.rb
sonar-server/src/main/webapp/WEB-INF/db/migrate/401_migrate_violations_to_issues.rb
sonar-server/src/test/java/org/sonar/server/db/DatabaseMigratorTest.java [deleted file]
sonar-server/src/test/java/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest.java [deleted file]
sonar-server/src/test/java/org/sonar/server/db/migrations/DatabaseMigratorTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ProgressTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/db/migrations/violation/SqlUtilTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ViolationConvertersTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ViolationMigrationTest.java [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations.xml [deleted file]
sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations_result.xml [deleted file]
sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/schema.sql [deleted file]
sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/migrate_violations.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/migrate_violations_result.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/no_violations_to_migrate.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/no_violations_to_migrate_result.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/schema.sql [new file with mode: 0644]

index 796625ca1be2d65bfd1ab123257ac5eda8f73d2a..e750985fd74380c61efa428b3715e13d47a4ad13 100644 (file)
@@ -55,6 +55,8 @@ import java.net.HttpURLConnection;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.List;
 import java.util.Map;
@@ -143,6 +145,28 @@ public class TestDatabase extends ExternalResource {
     }
   }
 
+  public int count(String sql) {
+    Connection connection = null;
+    PreparedStatement stmt = null;
+    ResultSet rs = null;
+    try {
+      connection = openConnection();
+      stmt = connection.prepareStatement(sql);
+      rs = stmt.executeQuery();
+      if (rs.next()) {
+        return rs.getInt(1);
+      }
+      throw new IllegalStateException("No results for " + sql);
+
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to execute sql: " + sql);
+
+    } finally {
+      DbUtils.closeQuietly(connection, stmt, rs);
+    }
+  }
+
+
   public void prepareDbUnit(Class testClass, String... testNames) {
     InputStream[] streams = new InputStream[testNames.length];
     try {
@@ -278,5 +302,4 @@ public class TestDatabase extends ExternalResource {
       IOUtils.closeQuietly(input);
     }
   }
-
 }
diff --git a/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigration.java b/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigration.java
deleted file mode 100644 (file)
index 2e99e4b..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.
- */
-package org.sonar.server.db;
-
-import org.sonar.core.persistence.Database;
-
-/**
- * Java alternative of ActiveRecord::Migration.
- * @since 3.7
- */
-public interface DatabaseMigration {
-
-  void execute(Database db);
-
-}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigrator.java b/sonar-server/src/main/java/org/sonar/server/db/DatabaseMigrator.java
deleted file mode 100644 (file)
index 6b36e05..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.
- */
-package org.sonar.server.db;
-
-import com.google.common.annotations.VisibleForTesting;
-import org.apache.commons.dbutils.DbUtils;
-import org.apache.ibatis.session.SqlSession;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.ServerComponent;
-import org.sonar.core.persistence.Database;
-import org.sonar.core.persistence.DdlUtils;
-import org.sonar.core.persistence.MyBatis;
-
-import java.sql.Connection;
-
-/**
- * Restore schema by executing DDL scripts. Only H2 database is supported.
- * Other databases are created by Ruby on Rails migrations.
- *
- * @since 2.12
- */
-public class DatabaseMigrator implements ServerComponent {
-
-  private final MyBatis myBatis;
-  private final Database database;
-
-  public DatabaseMigrator(MyBatis myBatis, Database database) {
-    this.myBatis = myBatis;
-    this.database = database;
-  }
-
-  /**
-   * @return true if the database has been created, false if this database is not supported
-   */
-  public boolean createDatabase() {
-    if (!DdlUtils.supportsDialect(database.getDialect().getId())) {
-      return false;
-    }
-
-    LoggerFactory.getLogger(getClass()).info("Create database");
-    SqlSession session = null;
-    Connection connection = null;
-    try {
-      session = myBatis.openSession();
-      connection = session.getConnection();
-      createSchema(connection, database.getDialect().getId());
-      return true;
-    } finally {
-      MyBatis.closeQuietly(session);
-
-      // The connection is probably already closed by session.close()
-      // but it's not documented in mybatis javadoc.
-      DbUtils.closeQuietly(connection);
-    }
-  }
-
-  public void executeMigration(String className) {
-    try {
-      Class<DatabaseMigration> migrationClass = (Class<DatabaseMigration>) Class.forName(className);
-      DatabaseMigration migration = migrationClass.newInstance();
-      migration.execute(database);
-
-    } catch (Exception e) {
-      // duplication between log and exception because webapp does not correctly log initial stacktrace
-      String msg = "Fail to execute database migration: " + className;
-      LoggerFactory.getLogger(getClass()).error(msg, e);
-      throw new IllegalStateException(msg, e);
-    }
-  }
-
-  @VisibleForTesting
-  protected void createSchema(Connection connection, String dialectId) {
-    DdlUtils.createSchema(connection, dialectId);
-  }
-}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/ConvertViolationsToIssues.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/ConvertViolationsToIssues.java
deleted file mode 100644 (file)
index be1d2c4..0000000
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.
- */
-package org.sonar.server.db.migrations;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import org.apache.commons.dbutils.DbUtils;
-import org.apache.commons.dbutils.QueryRunner;
-import org.apache.commons.dbutils.ResultSetHandler;
-import org.apache.commons.dbutils.handlers.AbstractListHandler;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.rule.Severity;
-import org.sonar.core.persistence.Database;
-import org.sonar.core.persistence.dialect.Oracle;
-import org.sonar.server.db.DatabaseMigration;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.sql.Connection;
-import java.sql.Date;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * Used in the Active Record Migration 401
- */
-public class ConvertViolationsToIssues implements DatabaseMigration {
-
-  private static final int GROUP_SIZE = 500;
-  private static final String PROJECT_ID = "projectId";
-  private static final String CREATED_AT = "createdAt";
-  private static final String REVIEW_ID = "reviewId";
-  private static final String SEVERITY = "severity";
-  private static final String REVIEW_STATUS = "reviewStatus";
-  private static final String REVIEW_MANUAL_SEVERITY = "reviewManualSeverity";
-  private static final String REVIEW_SEVERITY = "reviewSeverity";
-  private static final String REVIEW_UPDATED_AT = "reviewUpdatedAt";
-  private static final String ROOT_PROJECT_ID = "rootProjectId";
-  private static final String RULE_ID = "ruleId";
-  private static final String MESSAGE = "message";
-  private static final String LINE = "line";
-  private static final String COST = "cost";
-  private static final String CHECKSUM = "checksum";
-  private static final String REVIEW_RESOLUTION = "reviewResolution";
-  private static final String REVIEW_REPORTER_ID = "reviewReporterId";
-  private static final String REVIEW_ASSIGNEE_ID = "reviewAssigneeId";
-  private static final String REVIEW_DATA = "reviewData";
-  private static final String REVIEW_MANUAL_VIOLATION = "reviewManualViolation";
-  private static final String PLAN_ID = "planId";
-  private static final String ISSUE_KEY = "issueKey";
-
-  private static final String STATUS_OPEN = "OPEN";
-  private static final String STATUS_CONFIRMED = "CONFIRMED";
-  private static final String UPDATED_AT = "updatedAt";
-  private static final String REVIEW_TEXT = "reviewText";
-  private static final String USER_ID = "userId";
-  private static final String SEVERITY_MAJOR = "MAJOR";
-
-  private QueryRunner runner = new QueryRunner();
-
-  @Override
-  public void execute(Database db) {
-    Connection readConnection = null, writeConnection = null;
-    try {
-      readConnection = db.getDataSource().getConnection();
-      writeConnection = db.getDataSource().getConnection();
-      writeConnection.setAutoCommit(false);
-      truncateIssueTables(writeConnection);
-      convertViolations(readConnection, new Converter(db, runner, readConnection, writeConnection));
-    } catch (Exception e) {
-      throw new IllegalStateException("Fail to convert violations to issues", e);
-    } finally {
-      DbUtils.closeQuietly(readConnection);
-      DbUtils.closeQuietly(writeConnection);
-    }
-  }
-
-  private void truncateIssueTables(Connection writeConnection) throws SQLException {
-    // lower-case table names for SQLServer....
-    runner.update(writeConnection, "TRUNCATE TABLE issues");
-    runner.update(writeConnection, "TRUNCATE TABLE issue_changes");
-    writeConnection.commit();
-  }
-
-  private void convertViolations(Connection readConnection, Converter converter) throws SQLException {
-    runner.query(readConnection, "select id from rule_failures", new ViolationIdHandler(converter));
-  }
-
-
-  /**
-   * Browse violation ids and process them by groups of {@link #GROUP_SIZE}.
-   */
-  private static class ViolationIdHandler implements ResultSetHandler {
-    private Converter converter;
-    private Object[] violationIds = new Object[GROUP_SIZE];
-    private int cursor = 0;
-
-    private ViolationIdHandler(Converter converter) {
-      this.converter = converter;
-    }
-
-    @Override
-    public Object handle(ResultSet rs) throws SQLException {
-      int total = 0;
-      while (rs.next()) {
-        long violationId = rs.getLong(1);
-        violationIds[cursor++] = violationId;
-        if (cursor == GROUP_SIZE) {
-          convert();
-          Arrays.fill(violationIds, -1L);
-          cursor = 0;
-        }
-        total++;
-      }
-      if (cursor > 0) {
-        convert();
-      }
-      LoggerFactory.getLogger(getClass()).info(String.format("%d violations migrated to issues", total));
-      return null;
-    }
-
-    private void convert() throws SQLException {
-      if (cursor > 0) {
-        converter.convert(violationIds);
-      }
-    }
-  }
-
-  private static class Converter {
-    private static final long ONE_YEAR = 365L * 24 * 60 * 60 * 1000;
-    private String insertSql;
-    private String insertChangeSql;
-    private Date oneYearAgo = new Date(System.currentTimeMillis() - ONE_YEAR);
-    private QueryRunner runner;
-    private Connection readConnection, writeConnection;
-    private Map<Long, String> loginsByUserId;
-    private Map<Long, String> plansById;
-
-    private Converter(Database database, QueryRunner runner, Connection readConnection, Connection writeConnection) throws SQLException {
-      this.runner = runner;
-      this.readConnection = readConnection;
-      this.writeConnection = writeConnection;
-      initInsertSql(database);
-      initUsers();
-      initPlans();
-    }
-
-    private void initInsertSql(Database database) {
-      String issuesColumnsWithoutId = "kee, component_id, root_component_id, rule_id, severity, manual_severity, message, line, effort_to_fix, status, resolution, " +
-        "checksum, reporter, assignee, action_plan_key, issue_attributes, issue_creation_date, issue_update_date, created_at, updated_at";
-
-      if (Oracle.ID.equals(database.getDialect().getId())) {
-        insertSql = "INSERT INTO issues(id, "+ issuesColumnsWithoutId + ") " +
-          " VALUES (issues_seq.nextval, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
-        insertChangeSql = "INSERT INTO issue_changes(id, kee, issue_key, user_login, change_type, change_data, created_at, updated_at) " +
-          " VALUES (issue_changes_seq.nextval, ?, ?, ?, 'comment', ?, ?, ?)";
-      } else {
-        insertSql = "INSERT INTO issues("+ issuesColumnsWithoutId + ") " +
-          " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
-        insertChangeSql = "INSERT INTO issue_changes(kee, issue_key, user_login, change_type, change_data, created_at, updated_at) VALUES (?, ?, ?, 'comment', ?, ?, ?)";
-      }
-    }
-
-
-    private void initUsers() throws SQLException {
-      loginsByUserId = selectLongString("select id,login from users");
-    }
-
-    private void initPlans() throws SQLException {
-      plansById = selectLongString("select id,kee from action_plans");
-    }
-
-    private Map<Long, String> selectLongString(String sql) throws SQLException {
-      return runner.query(readConnection, sql, new ResultSetHandler<Map<Long, String>>() {
-        @Override
-        public Map<Long, String> handle(ResultSet rs) throws SQLException {
-          Map<Long, String> map = Maps.newHashMap();
-          while (rs.next()) {
-            map.put(rs.getLong(1), rs.getString(2));
-          }
-          return map;
-        }
-      });
-    }
-
-    /**
-     * Convert a group of maximum {@link #GROUP_SIZE} violations to issues
-     */
-    void convert(Object[] violationIds) throws SQLException {
-      List<Map<String, Object>> rows = runner.query(readConnection, ViolationHandler.SQL, new ViolationHandler(), violationIds);
-      List<Object[]> allParams = Lists.newArrayList();
-      List<Map<String, Object>> allComments = Lists.newArrayList();
-
-      for (Map<String, Object> row : rows) {
-        Long componentId = (Long) row.get(PROJECT_ID);
-        if (componentId == null) {
-          continue;
-        }
-        String issueKey = UUID.randomUUID().toString();
-        String status, severity, reporter = null;
-        boolean manualSeverity;
-        Object createdAt = Objects.firstNonNull(row.get(CREATED_AT), oneYearAgo);
-        Object updatedAt;
-        Long reviewId = (Long) row.get(REVIEW_ID);
-        if (reviewId == null) {
-          // violation without review
-          status = STATUS_OPEN;
-          manualSeverity = false;
-          severity = (String) row.get(SEVERITY);
-          updatedAt = createdAt;
-        } else {
-          // violation + review
-          String reviewStatus = (String) row.get(REVIEW_STATUS);
-          status = (STATUS_OPEN.equals(reviewStatus) ? STATUS_CONFIRMED : reviewStatus);
-          manualSeverity = Objects.firstNonNull((Boolean) row.get(REVIEW_MANUAL_SEVERITY), false);
-          severity = (String) row.get(REVIEW_SEVERITY);
-          updatedAt = Objects.firstNonNull(row.get(REVIEW_UPDATED_AT), oneYearAgo);
-          if ((Boolean) row.get(REVIEW_MANUAL_VIOLATION)) {
-            reporter = login((Long) row.get(REVIEW_REPORTER_ID));
-          }
-
-          List<Map<String, Object>> comments = runner.query(readConnection, ReviewCommentsHandler.SQL + reviewId, new ReviewCommentsHandler());
-          for (Map<String, Object> comment : comments) {
-            comment.put(ISSUE_KEY, issueKey);
-            allComments.add(comment);
-          }
-        }
-
-        Object[] params = new Object[20];
-        params[0] = issueKey;
-        params[1] = componentId;
-        params[2] = row.get(ROOT_PROJECT_ID);
-        params[3] = row.get(RULE_ID);
-        params[4] = severity;
-        params[5] = manualSeverity;
-        params[6] = row.get(MESSAGE);
-        params[7] = row.get(LINE);
-        params[8] = row.get(COST);
-        params[9] = status;
-        params[10] = row.get(REVIEW_RESOLUTION);
-        params[11] = row.get(CHECKSUM);
-        params[12] = reporter;
-        params[13] = login((Long) row.get(REVIEW_ASSIGNEE_ID));
-        params[14] = plan((Long) row.get(PLAN_ID));
-        params[15] = row.get(REVIEW_DATA);
-        params[16] = createdAt;
-        params[17] = updatedAt;
-        params[18] = createdAt;
-        params[19] = updatedAt;
-
-        allParams.add(params);
-      }
-      runner.batch(writeConnection, insertSql, allParams.toArray(new Object[allParams.size()][]));
-      writeConnection.commit();
-
-      insertComments(writeConnection, allComments);
-    }
-
-    private void insertComments(Connection writeConnection, List<Map<String, Object>> comments) throws SQLException {
-      List<Object[]> allParams = Lists.newArrayList();
-
-      for (Map<String, Object> comment : comments) {
-        String login = login((Long) comment.get(USER_ID));
-        if (login != null) {
-          Object[] params = new Object[6];
-          params[0] = UUID.randomUUID().toString();
-          params[1] = comment.get(ISSUE_KEY);
-          params[2] = login;
-          params[3] = comment.get(REVIEW_TEXT);
-          params[4] = comment.get(CREATED_AT);
-          params[5] = comment.get(UPDATED_AT);
-          allParams.add(params);
-        }
-      }
-      if (!allParams.isEmpty()) {
-        runner.batch(writeConnection, insertChangeSql, allParams.toArray(new Object[allParams.size()][]));
-        writeConnection.commit();
-      }
-    }
-
-    @CheckForNull
-    private String login(@Nullable Long userId) {
-      if (userId != null) {
-        return loginsByUserId.get(userId);
-      }
-      return null;
-    }
-
-    @CheckForNull
-    private String plan(@Nullable Long planId) {
-      if (planId != null) {
-        return plansById.get(planId);
-      }
-      return null;
-    }
-  }
-
-
-  private static class ViolationHandler extends AbstractListHandler<Map<String, Object>> {
-    static final String SQL;
-    static {
-      StringBuilder sb = new StringBuilder("select rev.id as reviewId, s.project_id as projectId, rf.rule_id as ruleId, " +
-        "  rf.failure_level as failureLevel, rf.message as message, rf.line as line, " +
-        "  rf.cost as cost, rf.created_at as createdAt, rf.checksum as checksum, rev.user_id as reviewReporterId, " +
-        "  rev.assignee_id as reviewAssigneeId, rev.status as reviewStatus, " +
-        "  rev.severity as reviewSeverity, rev.resolution as reviewResolution, rev.manual_severity as reviewManualSeverity, " +
-        "  rev.data as reviewData, rev.updated_at as reviewUpdatedAt, " +
-        "  s.root_project_id as rootProjectId, rev.manual_violation as reviewManualViolation, planreviews.action_plan_id as planId " +
-        " from rule_failures rf " +
-        " inner join snapshots s on s.id=rf.snapshot_id " +
-        " left join reviews rev on rev.rule_failure_permanent_id=rf.permanent_id " +
-        " left join action_plans_reviews planreviews on planreviews.review_id=rev.id " +
-        " where ");
-      for (int i = 0; i < GROUP_SIZE; i++) {
-        if (i > 0) {
-          sb.append(" or ");
-        }
-        sb.append("rf.id=?");
-      }
-      SQL = sb.toString();
-    }
-
-    private static final Map<Integer, String> SEVERITIES = ImmutableMap.of(1, Severity.INFO, 2, Severity.MINOR, 3, Severity.MAJOR, 4, Severity.CRITICAL, 5, Severity.BLOCKER);
-
-    @Override
-    protected Map<String, Object> handleRow(ResultSet rs) throws SQLException {
-      Map<String, Object> map = Maps.newHashMap();
-      map.put(REVIEW_ID, getLong(rs, REVIEW_ID));
-      map.put(PROJECT_ID, getLong(rs, PROJECT_ID));
-      map.put(ROOT_PROJECT_ID, getLong(rs, ROOT_PROJECT_ID));
-      map.put(RULE_ID, getLong(rs, RULE_ID));
-      map.put(SEVERITY, Objects.firstNonNull(SEVERITIES.get(getInt(rs, "failureLevel")), SEVERITY_MAJOR));
-      map.put(MESSAGE, rs.getString(MESSAGE));
-      map.put(LINE, getInt(rs, LINE));
-      map.put(COST, getDouble(rs, COST));
-      map.put(CHECKSUM, rs.getString(CHECKSUM));
-      map.put(CREATED_AT, rs.getTimestamp(CREATED_AT));
-      map.put(REVIEW_RESOLUTION, rs.getString(REVIEW_RESOLUTION));
-      map.put(REVIEW_SEVERITY, Objects.firstNonNull(rs.getString(REVIEW_SEVERITY), SEVERITY_MAJOR));
-      map.put(REVIEW_STATUS, rs.getString(REVIEW_STATUS));
-      map.put(REVIEW_REPORTER_ID, getLong(rs, REVIEW_REPORTER_ID));
-      map.put(REVIEW_ASSIGNEE_ID, getLong(rs, REVIEW_ASSIGNEE_ID));
-      map.put(REVIEW_DATA, rs.getString(REVIEW_DATA));
-      map.put(REVIEW_MANUAL_SEVERITY, rs.getBoolean(REVIEW_MANUAL_SEVERITY));
-      map.put(REVIEW_UPDATED_AT, rs.getTimestamp(REVIEW_UPDATED_AT));
-      map.put(REVIEW_MANUAL_VIOLATION, rs.getBoolean(REVIEW_MANUAL_VIOLATION));
-      map.put(PLAN_ID, getLong(rs, PLAN_ID));
-      return map;
-    }
-  }
-
-  private static class ReviewCommentsHandler extends AbstractListHandler<Map<String, Object>> {
-    static final String SQL = "select created_at as createdAt, updated_at as updatedAt, user_id as userId, review_text as reviewText from review_comments where review_id=";
-
-    @Override
-    protected Map<String, Object> handleRow(ResultSet rs) throws SQLException {
-      Map<String, Object> map = Maps.newHashMap();
-      map.put(CREATED_AT, rs.getTimestamp(CREATED_AT));
-      map.put(UPDATED_AT, rs.getTimestamp(UPDATED_AT));
-      map.put(USER_ID, getLong(rs, USER_ID));
-      map.put(REVIEW_TEXT, rs.getString(REVIEW_TEXT));
-      return map;
-    }
-  }
-
-  @CheckForNull
-  static Long getLong(ResultSet rs, String columnName) throws SQLException {
-    long l = rs.getLong(columnName);
-    return rs.wasNull() ? null : l;
-  }
-
-  @CheckForNull
-  static Double getDouble(ResultSet rs, String columnName) throws SQLException {
-    double d = rs.getDouble(columnName);
-    return rs.wasNull() ? null : d;
-  }
-
-  @CheckForNull
-  static Integer getInt(ResultSet rs, String columnName) throws SQLException {
-    int i = rs.getInt(columnName);
-    return rs.wasNull() ? null : i;
-  }
-
-}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigration.java
new file mode 100644 (file)
index 0000000..4126138
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations;
+
+/**
+ * Java alternative of ActiveRecord::Migration. Do not forget to declare implementation classes in {@link DatabaseMigrations#CLASSES}
+ * @since 3.7
+ */
+public interface DatabaseMigration {
+
+  void execute();
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrations.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrations.java
new file mode 100644 (file)
index 0000000..982042c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.server.db.migrations.violation.ViolationMigration;
+
+import java.util.List;
+
+public interface DatabaseMigrations {
+
+  List<Class<? extends DatabaseMigration>> CLASSES = ImmutableList.<Class<? extends DatabaseMigration>>of(
+    ViolationMigration.class
+  );
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
new file mode 100644 (file)
index 0000000..2a0a165
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.ibatis.session.SqlSession;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.Database;
+import org.sonar.core.persistence.DdlUtils;
+import org.sonar.core.persistence.MyBatis;
+
+import java.sql.Connection;
+
+/**
+ * Restore schema by executing DDL scripts. Only H2 database is supported.
+ * Other databases are created by Ruby on Rails migrations.
+ *
+ * @since 2.12
+ */
+public class DatabaseMigrator implements ServerComponent {
+
+  private final MyBatis myBatis;
+  private final Database database;
+  private final DatabaseMigration[] migrations;
+
+  public DatabaseMigrator(MyBatis myBatis, Database database, DatabaseMigration[] migrations) {
+    this.myBatis = myBatis;
+    this.database = database;
+    this.migrations = migrations;
+  }
+
+  /**
+   * @return true if the database has been created, false if this database is not supported
+   */
+  public boolean createDatabase() {
+    if (!DdlUtils.supportsDialect(database.getDialect().getId())) {
+      return false;
+    }
+
+    LoggerFactory.getLogger(getClass()).info("Create database");
+    SqlSession session = null;
+    Connection connection = null;
+    try {
+      session = myBatis.openSession();
+      connection = session.getConnection();
+      createSchema(connection, database.getDialect().getId());
+      return true;
+    } finally {
+      MyBatis.closeQuietly(session);
+
+      // The connection is probably already closed by session.close()
+      // but it's not documented in mybatis javadoc.
+      DbUtils.closeQuietly(connection);
+    }
+  }
+
+  public void executeMigration(String className) {
+    DatabaseMigration migration = getMigration(className);
+    try {
+      migration.execute();
+
+    } catch (Exception e) {
+      // duplication between log and exception because webapp does not correctly log initial stacktrace
+      String msg = "Fail to execute database migration: " + className;
+      LoggerFactory.getLogger(getClass()).error(msg, e);
+      throw new IllegalStateException(msg, e);
+    }
+  }
+
+  private DatabaseMigration getMigration(String className) {
+    for (DatabaseMigration migration : migrations) {
+      if (migration.getClass().getName().equals(className)) {
+        return migration;
+      }
+    }
+    throw new IllegalArgumentException("Database migration not found: " + className);
+  }
+
+  @VisibleForTesting
+  protected void createSchema(Connection connection, String dialectId) {
+    DdlUtils.createSchema(connection, dialectId);
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/Progress.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/Progress.java
new file mode 100644 (file)
index 0000000..d74f618
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This task logs every minute the status of migration. It is destroyed
+ * when migration is finished.
+ */
+class Progress extends TimerTask {
+
+  static final String THREAD_NAME = "Violation Migration Progress";
+  static final long DELAY_MS = 60000L;
+
+  private final AtomicInteger counter = new AtomicInteger(0);
+  private final Logger logger;
+  private final int totalViolations;
+  private final long start;
+
+  Progress(int totalViolations, Logger logger, long startDate) {
+    this.totalViolations = totalViolations;
+    this.logger = logger;
+    this.start = startDate;
+  }
+
+  Progress(int totalViolations) {
+    this(totalViolations, LoggerFactory.getLogger(Progress.class), System.currentTimeMillis());
+  }
+
+  void increment(int delta) {
+    counter.addAndGet(delta);
+  }
+
+  @Override
+  public void run() {
+    int totalIssues = counter.get();
+    long durationMinutes = (System.currentTimeMillis() - start) / 60000L;
+    int percents = (100 * totalIssues) / totalViolations;
+    if (totalIssues>0 && durationMinutes > 0) {
+      int frequency = (int) (totalIssues / durationMinutes);
+      int remaining = (totalViolations - totalIssues) / frequency;
+      logger.info(String.format(
+        "%d%% [%d/%d violations, %d minutes remaining]", percents, totalIssues, totalViolations, remaining)
+      );
+    } else {
+      logger.info(String.format("%d%% [%d/%d violations]", percents, totalIssues, totalViolations));
+    }
+
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/Referentials.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/Referentials.java
new file mode 100644 (file)
index 0000000..2260b16
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.dbutils.ResultSetHandler;
+import org.sonar.core.persistence.Database;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * Data loaded from database before migrating violations. It is
+ * shared amongst converter parallel tasks.
+ */
+class Referentials {
+
+  static final int VIOLATION_GROUP_SIZE = 1000;
+
+  private final Map<Long, String> loginsByUserId;
+  private final Map<Long, String> plansById;
+  private final Queue<long[]> groupsOfViolationIds;
+
+  // int is enough, it allows to upgrade up to 2 billions violations !
+  private int totalViolations = 0;
+
+  Referentials(Database database) throws SQLException {
+    loginsByUserId = selectLongString(database, "select id,login from users");
+    plansById = selectLongString(database, "select id,kee from action_plans");
+    groupsOfViolationIds = initGroupOfViolationIds(database);
+  }
+
+  @CheckForNull
+  String actionPlan(@Nullable Long id) {
+    return id != null ? plansById.get(id) : null;
+  }
+
+  @CheckForNull
+  String userLogin(@Nullable Long id) {
+    return id != null ? loginsByUserId.get(id) : null;
+  }
+
+  int totalViolations() {
+    return totalViolations;
+  }
+
+  Long[] pollGroupOfViolationIds() {
+    long[] longs = groupsOfViolationIds.poll();
+    if (longs == null) {
+      return new Long[0];
+    }
+    Long[] objects = new Long[longs.length];
+    for (int i = 0; i < longs.length; i++) {
+      objects[i] = Long.valueOf(longs[i]);
+    }
+    return objects;
+  }
+
+  private Map<Long, String> selectLongString(Database database, String sql) throws SQLException {
+    Connection connection = database.getDataSource().getConnection();
+    try {
+      return new QueryRunner().query(connection, sql, new ResultSetHandler<Map<Long, String>>() {
+        @Override
+        public Map<Long, String> handle(ResultSet rs) throws SQLException {
+          Map<Long, String> map = Maps.newHashMap();
+          while (rs.next()) {
+            map.put(rs.getLong(1), rs.getString(2));
+          }
+          return map;
+        }
+      });
+    } finally {
+      DbUtils.closeQuietly(connection);
+    }
+  }
+
+  private Queue<long[]> initGroupOfViolationIds(Database database) throws SQLException {
+    Connection connection = database.getDataSource().getConnection();
+    Statement stmt = null;
+    ResultSet rs = null;
+    try {
+      connection.setAutoCommit(false);
+      stmt = connection.createStatement();
+      stmt.setFetchSize(10000);
+      rs = stmt.executeQuery("select id from rule_failures");
+      ConcurrentLinkedQueue<long[]> queue = new ConcurrentLinkedQueue<long[]>();
+
+      totalViolations = 0;
+      long[] block = new long[VIOLATION_GROUP_SIZE];
+      int cursor = 0;
+      while (rs.next()) {
+        block[cursor++] = rs.getLong(1);
+        totalViolations++;
+        if (cursor == VIOLATION_GROUP_SIZE) {
+          queue.add(block);
+          block = new long[VIOLATION_GROUP_SIZE];
+          cursor = 0;
+        }
+      }
+      if (cursor > 0) {
+        queue.add(block);
+      }
+      return queue;
+    } finally {
+      DbUtils.closeQuietly(connection, stmt, rs);
+    }
+  }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/SqlUtil.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/SqlUtil.java
new file mode 100644 (file)
index 0000000..df1a9d8
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import org.slf4j.Logger;
+
+import javax.annotation.CheckForNull;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+class SqlUtil {
+
+  private SqlUtil() {
+    // only static methods
+  }
+
+  /**
+   * Logback does not log exceptions associated to {@link java.sql.SQLException#getNextException()}.
+   * See http://jira.qos.ch/browse/LOGBACK-775
+   */
+  static void log(Logger logger, SQLException e) {
+    SQLException next = e.getNextException();
+    while (next != null) {
+      logger.error("SQL error: {}. Message: {}", next.getSQLState(), next.getMessage());
+      next = next.getNextException();
+    }
+  }
+
+  @CheckForNull
+  static Long getLong(ResultSet rs, String columnName) throws SQLException {
+    long l = rs.getLong(columnName);
+    return rs.wasNull() ? null : l;
+  }
+
+  @CheckForNull
+  static Double getDouble(ResultSet rs, String columnName) throws SQLException {
+    double d = rs.getDouble(columnName);
+    return rs.wasNull() ? null : d;
+  }
+
+  @CheckForNull
+  static Integer getInt(ResultSet rs, String columnName) throws SQLException {
+    int i = rs.getInt(columnName);
+    return rs.wasNull() ? null : i;
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationConverter.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationConverter.java
new file mode 100644 (file)
index 0000000..de525fc
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.dbutils.handlers.AbstractListHandler;
+import org.sonar.api.rule.Severity;
+import org.sonar.core.persistence.Database;
+
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+class ViolationConverter implements Callable<Object> {
+
+  private static final long ONE_YEAR = 365L * 24 * 60 * 60 * 1000;
+  private static final Date ONE_YEAR_AGO = new Date(System.currentTimeMillis() - ONE_YEAR);
+
+  private static final String PROJECT_ID = "projectId";
+  private static final String CREATED_AT = "createdAt";
+  private static final String REVIEW_ID = "reviewId";
+  private static final String SEVERITY = "severity";
+  private static final String REVIEW_STATUS = "reviewStatus";
+  private static final String REVIEW_MANUAL_SEVERITY = "reviewManualSeverity";
+  private static final String REVIEW_SEVERITY = "reviewSeverity";
+  private static final String REVIEW_UPDATED_AT = "reviewUpdatedAt";
+  private static final String ROOT_PROJECT_ID = "rootProjectId";
+  private static final String RULE_ID = "ruleId";
+  private static final String MESSAGE = "message";
+  private static final String LINE = "line";
+  private static final String COST = "cost";
+  private static final String CHECKSUM = "checksum";
+  private static final String REVIEW_RESOLUTION = "reviewResolution";
+  private static final String REVIEW_REPORTER_ID = "reviewReporterId";
+  private static final String REVIEW_ASSIGNEE_ID = "reviewAssigneeId";
+  private static final String REVIEW_DATA = "reviewData";
+  private static final String REVIEW_MANUAL_VIOLATION = "reviewManualViolation";
+  private static final String PLAN_ID = "planId";
+  private static final String ISSUE_KEY = "issueKey";
+  private static final String STATUS_OPEN = "OPEN";
+  private static final String STATUS_CONFIRMED = "CONFIRMED";
+  private static final String UPDATED_AT = "updatedAt";
+  private static final String REVIEW_TEXT = "reviewText";
+  private static final String USER_ID = "userId";
+  private static final String SEVERITY_MAJOR = "MAJOR";
+
+
+  private static final String SQL_ISSUE_COLUMNS = "kee, component_id, root_component_id, rule_id, severity, manual_severity, message, line, effort_to_fix, status, resolution, " +
+    "checksum, reporter, assignee, action_plan_key, issue_attributes, issue_creation_date, issue_update_date, created_at, updated_at";
+
+  private static final String SQL_INSERT_ISSUE = "INSERT INTO issues(" + SQL_ISSUE_COLUMNS + ")" +
+    " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+  private static final String SQL_INSERT_ISSUE_CHANGE = "INSERT INTO issue_changes(kee, issue_key, user_login, change_type, change_data, created_at, updated_at)" +
+    " VALUES (?, ?, ?, 'comment', ?, ?, ?)";
+
+  private static final String SQL_DELETE_RULE_FAILURES;
+
+  static {
+    StringBuilder sb = new StringBuilder("delete rule_failures where ");
+    for (int i = 0; i < Referentials.VIOLATION_GROUP_SIZE; i++) {
+      if (i > 0) {
+        sb.append(" or ");
+      }
+      sb.append("id=?");
+    }
+    SQL_DELETE_RULE_FAILURES = sb.toString();
+  }
+
+  static final String SQL_SELECT_RULE_FAILURES;
+
+  static {
+    StringBuilder sb = new StringBuilder("select rev.id as reviewId, s.project_id as projectId, rf.rule_id as ruleId, " +
+      "  rf.failure_level as failureLevel, rf.message as message, rf.line as line, " +
+      "  rf.cost as cost, rf.created_at as createdAt, rf.checksum as checksum, rev.user_id as reviewReporterId, " +
+      "  rev.assignee_id as reviewAssigneeId, rev.status as reviewStatus, " +
+      "  rev.severity as reviewSeverity, rev.resolution as reviewResolution, rev.manual_severity as reviewManualSeverity, " +
+      "  rev.data as reviewData, rev.updated_at as reviewUpdatedAt, " +
+      "  s.root_project_id as rootProjectId, rev.manual_violation as reviewManualViolation, planreviews.action_plan_id as planId " +
+      " from rule_failures rf " +
+      " inner join snapshots s on s.id=rf.snapshot_id " +
+      " left join reviews rev on rev.rule_failure_permanent_id=rf.permanent_id " +
+      " left join action_plans_reviews planreviews on planreviews.review_id=rev.id " +
+      " where ");
+    for (int i = 0; i < Referentials.VIOLATION_GROUP_SIZE; i++) {
+      if (i > 0) {
+        sb.append(" or ");
+      }
+      sb.append("rf.id=?");
+    }
+    SQL_SELECT_RULE_FAILURES = sb.toString();
+  }
+
+  private final Database db;
+  private final Referentials referentials;
+  private final Progress progress;
+
+  ViolationConverter(Referentials referentials, Database db, Progress progress) {
+    this.referentials = referentials;
+    this.db = db;
+    this.progress = progress;
+  }
+
+  @Override
+  public Object call() throws Exception {
+    // For each group of 1000 violation ids:
+    // - load related violations, reviews and action plans
+    // - in a transaction
+    //   -- insert issues
+    //   -- insert issue_changes if there are review comments
+    //   -- delete violations
+
+    Long[] violationIds = referentials.pollGroupOfViolationIds();
+    while (violationIds.length>0) {
+      List<Map<String, Object>> rows = selectRows(violationIds);
+      convert(rows, violationIds);
+
+      violationIds = referentials.pollGroupOfViolationIds();
+    }
+    return null;
+  }
+
+  private List<Map<String, Object>> selectRows(Long[] violationIds) throws SQLException {
+    Connection readConnection = null;
+    try {
+      readConnection = db.getDataSource().getConnection();
+      ViolationHandler violationHandler = new ViolationHandler();
+      return new QueryRunner().query(readConnection, SQL_SELECT_RULE_FAILURES, violationHandler, violationIds);
+
+    } finally {
+      DbUtils.closeQuietly(readConnection);
+    }
+  }
+
+  private void convert(List<Map<String, Object>> rows, Long[] violationIds) throws SQLException {
+    Connection readConnection = null;
+    Connection writeConnection = null;
+    try {
+      readConnection = db.getDataSource().getConnection();
+      writeConnection = db.getDataSource().getConnection();
+      writeConnection.setAutoCommit(false);
+
+      List<Object[]> allParams = Lists.newArrayList();
+      List<Map<String, Object>> allComments = Lists.newArrayList();
+
+      QueryRunner runner = new QueryRunner();
+      for (Map<String, Object> row : rows) {
+        Long componentId = (Long) row.get(PROJECT_ID);
+        if (componentId == null) {
+          continue;
+        }
+        String issueKey = UUID.randomUUID().toString();
+        String status, severity, reporter = null;
+        boolean manualSeverity;
+        Object createdAt = Objects.firstNonNull(row.get(CREATED_AT), ONE_YEAR_AGO);
+        Object updatedAt;
+        Long reviewId = (Long) row.get(REVIEW_ID);
+        if (reviewId == null) {
+          // violation without review
+          status = STATUS_OPEN;
+          manualSeverity = false;
+          severity = (String) row.get(SEVERITY);
+          updatedAt = createdAt;
+        } else {
+          // violation + review
+          String reviewStatus = (String) row.get(REVIEW_STATUS);
+          status = (STATUS_OPEN.equals(reviewStatus) ? STATUS_CONFIRMED : reviewStatus);
+          manualSeverity = Objects.firstNonNull((Boolean) row.get(REVIEW_MANUAL_SEVERITY), false);
+          severity = (String) row.get(REVIEW_SEVERITY);
+          updatedAt = Objects.firstNonNull(row.get(REVIEW_UPDATED_AT), ONE_YEAR_AGO);
+          if ((Boolean) row.get(REVIEW_MANUAL_VIOLATION)) {
+            reporter = referentials.userLogin((Long) row.get(REVIEW_REPORTER_ID));
+          }
+
+          List<Map<String, Object>> comments = runner.query(readConnection, ReviewCommentsHandler.SQL + reviewId, new ReviewCommentsHandler());
+          for (Map<String, Object> comment : comments) {
+            comment.put(ISSUE_KEY, issueKey);
+            allComments.add(comment);
+          }
+        }
+        Object[] params = new Object[20];
+        params[0] = issueKey;
+        params[1] = componentId;
+        params[2] = row.get(ROOT_PROJECT_ID);
+        params[3] = row.get(RULE_ID);
+        params[4] = severity;
+        params[5] = manualSeverity;
+        params[6] = row.get(MESSAGE);
+        params[7] = row.get(LINE);
+        params[8] = row.get(COST);
+        params[9] = status;
+        params[10] = row.get(REVIEW_RESOLUTION);
+        params[11] = row.get(CHECKSUM);
+        params[12] = reporter;
+        params[13] = referentials.userLogin((Long) row.get(REVIEW_ASSIGNEE_ID));
+        params[14] = referentials.actionPlan((Long) row.get(PLAN_ID));
+        params[15] = row.get(REVIEW_DATA);
+        params[16] = createdAt;
+        params[17] = updatedAt;
+        params[18] = createdAt;
+        params[19] = updatedAt;
+        allParams.add(params);
+      }
+      runner.batch(writeConnection, SQL_INSERT_ISSUE, allParams.toArray(new Object[allParams.size()][]));
+      insertComments(writeConnection, allComments);
+      runner.update(writeConnection, SQL_DELETE_RULE_FAILURES, violationIds);
+      writeConnection.commit();
+      progress.increment(rows.size());
+
+    } finally {
+      DbUtils.closeQuietly(readConnection);
+      DbUtils.closeQuietly(writeConnection);
+    }
+  }
+
+  private void insertComments(Connection writeConnection, List<Map<String, Object>> comments) throws SQLException {
+    List<Object[]> allParams = Lists.newArrayList();
+
+    for (Map<String, Object> comment : comments) {
+      String login = referentials.userLogin((Long) comment.get(USER_ID));
+      if (login != null) {
+        Object[] params = new Object[6];
+        params[0] = UUID.randomUUID().toString();
+        params[1] = comment.get(ISSUE_KEY);
+        params[2] = login;
+        params[3] = comment.get(REVIEW_TEXT);
+        params[4] = comment.get(CREATED_AT);
+        params[5] = comment.get(UPDATED_AT);
+        allParams.add(params);
+      }
+    }
+    if (!allParams.isEmpty()) {
+      new QueryRunner().batch(writeConnection, SQL_INSERT_ISSUE_CHANGE, allParams.toArray(new Object[allParams.size()][]));
+    }
+  }
+
+
+  private static class ReviewCommentsHandler extends AbstractListHandler<Map<String, Object>> {
+    static final String SQL = "select created_at as createdAt, updated_at as updatedAt, user_id as userId, review_text as reviewText from review_comments where review_id=";
+
+    @Override
+    protected Map<String, Object> handleRow(ResultSet rs) throws SQLException {
+      Map<String, Object> map = Maps.newHashMap();
+      map.put(CREATED_AT, rs.getTimestamp(CREATED_AT));
+      map.put(UPDATED_AT, rs.getTimestamp(UPDATED_AT));
+      map.put(USER_ID, SqlUtil.getLong(rs, USER_ID));
+      map.put(REVIEW_TEXT, rs.getString(REVIEW_TEXT));
+      return map;
+    }
+  }
+
+  private static class ViolationHandler extends AbstractListHandler<Map<String, Object>> {
+    private static final Map<Integer, String> SEVERITIES = ImmutableMap.of(1, Severity.INFO, 2, Severity.MINOR, 3, Severity.MAJOR, 4, Severity.CRITICAL, 5, Severity.BLOCKER);
+
+
+    @Override
+    protected Map<String, Object> handleRow(ResultSet rs) throws SQLException {
+      Map<String, Object> map = Maps.newHashMap();
+      map.put(REVIEW_ID, SqlUtil.getLong(rs, REVIEW_ID));
+      map.put(PROJECT_ID, SqlUtil.getLong(rs, PROJECT_ID));
+      map.put(ROOT_PROJECT_ID, SqlUtil.getLong(rs, ROOT_PROJECT_ID));
+      map.put(RULE_ID, SqlUtil.getLong(rs, RULE_ID));
+      map.put(SEVERITY, Objects.firstNonNull(SEVERITIES.get(SqlUtil.getInt(rs, "failureLevel")), SEVERITY_MAJOR));
+      map.put(MESSAGE, rs.getString(MESSAGE));
+      map.put(LINE, SqlUtil.getInt(rs, LINE));
+      map.put(COST, SqlUtil.getDouble(rs, COST));
+      map.put(CHECKSUM, rs.getString(CHECKSUM));
+      map.put(CREATED_AT, rs.getTimestamp(CREATED_AT));
+      map.put(REVIEW_RESOLUTION, rs.getString(REVIEW_RESOLUTION));
+      map.put(REVIEW_SEVERITY, Objects.firstNonNull(rs.getString(REVIEW_SEVERITY), SEVERITY_MAJOR));
+      map.put(REVIEW_STATUS, rs.getString(REVIEW_STATUS));
+      map.put(REVIEW_REPORTER_ID, SqlUtil.getLong(rs, REVIEW_REPORTER_ID));
+      map.put(REVIEW_ASSIGNEE_ID, SqlUtil.getLong(rs, REVIEW_ASSIGNEE_ID));
+      map.put(REVIEW_DATA, rs.getString(REVIEW_DATA));
+      map.put(REVIEW_MANUAL_SEVERITY, rs.getBoolean(REVIEW_MANUAL_SEVERITY));
+      map.put(REVIEW_UPDATED_AT, rs.getTimestamp(REVIEW_UPDATED_AT));
+      map.put(REVIEW_MANUAL_VIOLATION, rs.getBoolean(REVIEW_MANUAL_VIOLATION));
+      map.put(PLAN_ID, SqlUtil.getLong(rs, PLAN_ID));
+      return map;
+    }
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationConverters.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationConverters.java
new file mode 100644 (file)
index 0000000..640903d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import com.google.common.collect.Lists;
+import org.sonar.api.config.Settings;
+import org.sonar.core.persistence.Database;
+
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.*;
+
+class ViolationConverters {
+
+  static final int DEFAULT_THREADS = 5;
+  static final String THREADS_PROPERTY = "sonar.violationMigration.threads";
+  private final Settings settings;
+
+  ViolationConverters(Settings settings) {
+    this.settings = settings;
+  }
+
+  void execute(Referentials referentials, Database db) throws Exception {
+    Progress progress = new Progress(referentials.totalViolations());
+
+    List<Callable<Object>> converters = Lists.newArrayList();
+    for (int i = 0; i < numberOfThreads(); i++) {
+      converters.add(new ViolationConverter(referentials, db, progress));
+    }
+
+    doExecute(progress, converters);
+  }
+
+  void doExecute(TimerTask progress, List<Callable<Object>> converters) throws InterruptedException, ExecutionException {
+    Timer timer = new Timer(Progress.THREAD_NAME);
+    timer.schedule(progress, Progress.DELAY_MS, Progress.DELAY_MS);
+    try {
+      ExecutorService executor = Executors.newFixedThreadPool(converters.size());
+      List<Future<Object>> results = executor.invokeAll(converters);
+      executor.shutdown();
+      for (Future result : results) {
+        result.get();
+      }
+    } finally {
+      timer.cancel();
+    }
+  }
+
+  int numberOfThreads() {
+    int threads = settings.getInt(THREADS_PROPERTY);
+    if (threads < 0) {
+      throw new IllegalArgumentException(String.format("Bad value of %s: %d", THREADS_PROPERTY, threads));
+    }
+    if (threads == 0) {
+      threads = DEFAULT_THREADS;
+    }
+    return threads;
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationMigration.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/ViolationMigration.java
new file mode 100644 (file)
index 0000000..88c60a8
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.MessageException;
+import org.sonar.core.persistence.Database;
+import org.sonar.server.db.migrations.DatabaseMigration;
+
+import java.sql.SQLException;
+
+/**
+ * Used in the Active Record Migration 401
+ */
+public class ViolationMigration implements DatabaseMigration {
+
+  private final Settings settings;
+
+  private Logger logger = LoggerFactory.getLogger(ViolationMigration.class);
+  private final Database db;
+
+  public ViolationMigration(Database database, Settings settings) {
+    this.db = database;
+    this.settings = settings;
+  }
+
+  @Override
+  public void execute() {
+    try {
+      migrate();
+
+    } catch (SQLException e) {
+      logger.error("Fail to convert violations to issues", e);
+      SqlUtil.log(logger, e);
+      throw MessageException.of("Fail to convert violations to issues");
+
+    } catch (Exception e) {
+      logger.error("Fail to convert violations to issues", e);
+      throw MessageException.of("Fail to convert violations to issues");
+    }
+  }
+
+  public void migrate() throws Exception {
+    logger.info("Initialize input");
+    Referentials referentials = new Referentials(db);
+
+    if (referentials.totalViolations() > 0) {
+      logger.info("Migrate {} violations", referentials.totalViolations());
+
+      ViolationConverters converters = new ViolationConverters(settings);
+      converters.execute(referentials, db);
+    }
+  }
+
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/package-info.java b/sonar-server/src/main/java/org/sonar/server/db/migrations/violation/package-info.java
new file mode 100644 (file)
index 0000000..969ef6e
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.db.migrations.violation;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 55f07767e23b92b28493d3cd4576af2046b55918..e6cabb36702030a86a0b9fa8cb602a5dc4b93c0f 100644 (file)
@@ -81,7 +81,9 @@ import org.sonar.server.component.DefaultComponentFinder;
 import org.sonar.server.component.DefaultRubyComponentService;
 import org.sonar.server.configuration.Backup;
 import org.sonar.server.configuration.ProfilesManager;
-import org.sonar.server.db.DatabaseMigrator;
+import org.sonar.server.db.migrations.DatabaseMigration;
+import org.sonar.server.db.migrations.DatabaseMigrations;
+import org.sonar.server.db.migrations.DatabaseMigrator;
 import org.sonar.server.db.EmbeddedDatabaseFactory;
 import org.sonar.server.issue.ActionPlanService;
 import org.sonar.server.issue.ActionService;
@@ -224,6 +226,9 @@ public final class Platform {
     rootContainer.addSingleton(MyBatis.class);
     rootContainer.addSingleton(DefaultServerUpgradeStatus.class);
     rootContainer.addSingleton(DatabaseServerCompatibility.class);
+    for (Class<? extends DatabaseMigration> migrationClass : DatabaseMigrations.CLASSES) {
+      rootContainer.addSingleton(migrationClass);
+    }
     rootContainer.addSingleton(DatabaseMigrator.class);
     rootContainer.addSingleton(DatabaseVersion.class);
     for (Class daoClass : DaoUtils.getDaoClasses()) {
index 340eb9ef3a1601b111e0b9439aa03c0f4d04dbc7..807cb564be7c689bd34f4eadd3fca95c802c43a9 100644 (file)
@@ -57,7 +57,7 @@ import org.sonar.core.resource.ResourceKeyUpdaterDao;
 import org.sonar.core.timemachine.Periods;
 import org.sonar.server.configuration.Backup;
 import org.sonar.server.configuration.ProfilesManager;
-import org.sonar.server.db.DatabaseMigrator;
+import org.sonar.server.db.migrations.DatabaseMigrator;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.platform.ServerIdGenerator;
 import org.sonar.server.platform.ServerSettings;
index 3d6ea3cf3d15a9b080a3d6365ce4d8a6d5c210ea..32cfe4c771ec97943d644d8470cbb9653942106c 100644 (file)
@@ -163,6 +163,11 @@ class ActiveRecord::Migration
     execute_ddl("drop trigger #{trigger_name}", "DROP TRIGGER #{trigger_name}")
   end
 
+  def self.write(text="")
+    # See migration.rb, the method write directly calls "puts"
+    Java::OrgSlf4j::LoggerFactory::getLogger('DbMigration').info(text) if verbose
+  end
+
   private
 
   def self.execute_ddl(message, ddl)
index f1e7a5cd349318b8b8c1bdd1da24214ec5df7d70..20bb8d034a6c3a142579313e4bdd121d004bdc02 100644 (file)
@@ -25,7 +25,7 @@
 class MigrateViolationsToIssues < ActiveRecord::Migration
 
   def self.up
-    Java::OrgSonarServerUi::JRubyFacade.getInstance().databaseMigrator().executeMigration('org.sonar.server.db.migrations.ConvertViolationsToIssues')
+    Java::OrgSonarServerUi::JRubyFacade.getInstance().databaseMigrator().executeMigration('org.sonar.server.db.migrations.violation.ViolationMigration')
 
     # Currently not possible in Java because of Oracle (triggers and sequences must be dropped)
     drop_table('rule_failures')
@@ -34,4 +34,4 @@ class MigrateViolationsToIssues < ActiveRecord::Migration
     drop_table('action_plans_reviews')
   end
 
-end
\ No newline at end of file
+end
diff --git a/sonar-server/src/test/java/org/sonar/server/db/DatabaseMigratorTest.java b/sonar-server/src/test/java/org/sonar/server/db/DatabaseMigratorTest.java
deleted file mode 100644 (file)
index 2cc20b3..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.
- */
-package org.sonar.server.db;
-
-import org.apache.ibatis.session.SqlSession;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.core.persistence.AbstractDaoTestCase;
-import org.sonar.core.persistence.Database;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.persistence.dialect.Dialect;
-import org.sonar.core.persistence.dialect.H2;
-import org.sonar.core.persistence.dialect.MySql;
-
-import java.sql.Connection;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class DatabaseMigratorTest extends AbstractDaoTestCase {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  MyBatis mybatis = mock(MyBatis.class);
-  Database database = mock(Database.class);
-
-  @Test
-  public void should_support_only_creation_of_h2_database() throws Exception {
-    when(database.getDialect()).thenReturn(new MySql());
-
-    DatabaseMigrator migrator = new DatabaseMigrator(mybatis, database);
-
-    assertThat(migrator.createDatabase()).isFalse();
-    verifyZeroInteractions(mybatis);
-  }
-
-  @Test
-  public void fail_if_execute_unknown_migration() throws Exception {
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Fail to execute database migration: org.xxx.UnknownMigration");
-
-    DatabaseMigrator migrator = new DatabaseMigrator(mybatis, database);
-    migrator.executeMigration("org.xxx.UnknownMigration");
-  }
-
-  @Test
-  public void fail_if_execute_not_a_migration() throws Exception {
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Fail to execute database migration: java.lang.String");
-
-    DatabaseMigrator migrator = new DatabaseMigrator(mybatis, database);
-    migrator.executeMigration("java.lang.String");
-  }
-
-  @Test
-  public void execute_migration() throws Exception {
-    DatabaseMigrator migrator = new DatabaseMigrator(mybatis, database);
-    assertThat(FakeMigration.executed).isFalse();
-    migrator.executeMigration(FakeMigration.class.getName());
-    assertThat(FakeMigration.executed).isTrue();
-  }
-
-  @Test
-  public void should_create_schema_on_h2() throws Exception {
-
-    Dialect supportedDialect = new H2();
-    when(database.getDialect()).thenReturn(supportedDialect);
-    Connection connection = mock(Connection.class);
-    SqlSession session = mock(SqlSession.class);
-    when(session.getConnection()).thenReturn(connection);
-    when(mybatis.openSession()).thenReturn(session);
-
-    DatabaseMigrator databaseMigrator = new DatabaseMigrator(mybatis, database) {
-      @Override
-      protected void createSchema(Connection connection, String dialectId) {
-      }
-    };
-
-    assertThat(databaseMigrator.createDatabase()).isTrue();
-  }
-
-  public static class FakeMigration implements DatabaseMigration {
-    static boolean executed = false;
-    @Override
-    public void execute(Database db) {
-      executed = true;
-    }
-  }
-}
diff --git a/sonar-server/src/test/java/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest.java b/sonar-server/src/test/java/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest.java
deleted file mode 100644 (file)
index c2ab498..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.
- */
-package org.sonar.server.db.migrations;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.core.persistence.TestDatabase;
-
-public class ConvertViolationsToIssuesTest {
-
-  @Rule
-  public TestDatabase db = new TestDatabase().schema(getClass(), "schema.sql");
-
-  @Test
-  public void convert_violations() throws Exception {
-    db.prepareDbUnit(getClass(), "convert_violations.xml");
-
-    new ConvertViolationsToIssues().execute(db.database());
-
-    db.assertDbUnit(getClass(), "convert_violations_result.xml", "issues", "issue_changes");
-  }
-}
diff --git a/sonar-server/src/test/java/org/sonar/server/db/migrations/DatabaseMigratorTest.java b/sonar-server/src/test/java/org/sonar/server/db/migrations/DatabaseMigratorTest.java
new file mode 100644 (file)
index 0000000..a8e3d06
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.Database;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.persistence.dialect.Dialect;
+import org.sonar.core.persistence.dialect.H2;
+import org.sonar.core.persistence.dialect.MySql;
+
+import java.sql.Connection;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DatabaseMigratorTest extends AbstractDaoTestCase {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  MyBatis mybatis = mock(MyBatis.class);
+  Database database = mock(Database.class);
+  DatabaseMigration[] migrations = new DatabaseMigration[]{new FakeMigration()};
+
+  @Test
+  public void should_support_only_creation_of_h2_database() throws Exception {
+    when(database.getDialect()).thenReturn(new MySql());
+
+    DatabaseMigrator migrator = new DatabaseMigrator(mybatis, database, migrations);
+
+    assertThat(migrator.createDatabase()).isFalse();
+    verifyZeroInteractions(mybatis);
+  }
+
+  @Test
+  public void fail_if_execute_unknown_migration() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Database migration not found: org.xxx.UnknownMigration");
+
+    DatabaseMigrator migrator = new DatabaseMigrator(mybatis, database, migrations);
+    migrator.executeMigration("org.xxx.UnknownMigration");
+  }
+
+  @Test
+  public void execute_migration() throws Exception {
+    DatabaseMigrator migrator = new DatabaseMigrator(mybatis, database, migrations);
+    assertThat(FakeMigration.executed).isFalse();
+    migrator.executeMigration(FakeMigration.class.getName());
+    assertThat(FakeMigration.executed).isTrue();
+  }
+
+  @Test
+  public void should_create_schema_on_h2() throws Exception {
+
+    Dialect supportedDialect = new H2();
+    when(database.getDialect()).thenReturn(supportedDialect);
+    Connection connection = mock(Connection.class);
+    SqlSession session = mock(SqlSession.class);
+    when(session.getConnection()).thenReturn(connection);
+    when(mybatis.openSession()).thenReturn(session);
+
+    DatabaseMigrator databaseMigrator = new DatabaseMigrator(mybatis, database, migrations) {
+      @Override
+      protected void createSchema(Connection connection, String dialectId) {
+      }
+    };
+
+    assertThat(databaseMigrator.createDatabase()).isTrue();
+  }
+
+  public static class FakeMigration implements DatabaseMigration {
+    static boolean executed = false;
+    @Override
+    public void execute() {
+      executed = true;
+    }
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ProgressTest.java b/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ProgressTest.java
new file mode 100644 (file)
index 0000000..87c0e2f
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.slf4j.Logger;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class ProgressTest {
+  @Test
+  public void log_progress() throws Exception {
+    Logger logger = mock(Logger.class);
+    ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
+
+    Progress progress = new Progress(5000, logger, System.currentTimeMillis());
+    progress.run();
+    progress.increment(200);
+    progress.increment(130);
+    progress.run();
+    progress.increment(1670);
+    progress.run();
+
+    verify(logger, times(3)).info(argument.capture());
+    assertThat(argument.getAllValues().get(0)).isEqualTo("0% [0/5000 violations]");
+    assertThat(argument.getAllValues().get(1)).isEqualTo("6% [330/5000 violations]");
+    assertThat(argument.getAllValues().get(2)).isEqualTo("40% [2000/5000 violations]");
+  }
+
+  @Test
+  public void log_remaining_time() throws Exception {
+    Logger logger = mock(Logger.class);
+    ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
+
+    long fiveMinutesAgo = System.currentTimeMillis() - 5 * 60 * 1000;
+    Progress progress = new Progress(5000, logger, fiveMinutesAgo);
+    progress.increment(2000);
+    progress.run();
+
+    verify(logger).info(argument.capture());
+    assertThat(argument.getValue()).isEqualTo("40% [2000/5000 violations, 7 minutes remaining]");
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/SqlUtilTest.java b/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/SqlUtilTest.java
new file mode 100644 (file)
index 0000000..69c1593
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+
+import java.sql.SQLException;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class SqlUtilTest {
+
+  @Test
+  public void log_all_sql_exceptions() {
+    SQLException root = new SQLException("this is root", "123");
+    SQLException next = new SQLException("this is next", "456");
+    root.setNextException(next);
+
+    Logger logger = mock(Logger.class);
+    SqlUtil.log(logger, root);
+
+    verify(logger).error("SQL error: {}. Message: {}", "456", "this is next");
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ViolationConvertersTest.java b/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ViolationConvertersTest.java
new file mode 100644 (file)
index 0000000..9b144fe
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+
+import java.util.List;
+import java.util.TimerTask;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ViolationConvertersTest {
+
+  @Test
+  public void default_number_of_threads() throws Exception {
+    assertThat(new ViolationConverters(new Settings()).numberOfThreads()).isEqualTo(ViolationConverters.DEFAULT_THREADS);
+  }
+
+  @Test
+  public void configure_number_of_threads() throws Exception {
+    Settings settings = new Settings();
+    settings.setProperty(ViolationConverters.THREADS_PROPERTY, 2);
+    assertThat(new ViolationConverters(settings).numberOfThreads()).isEqualTo(2);
+  }
+
+  @Test
+  public void number_of_threads_should_not_be_negative() throws Exception {
+    try {
+      Settings settings = new Settings();
+      settings.setProperty(ViolationConverters.THREADS_PROPERTY, -2);
+      new ViolationConverters(settings).numberOfThreads();
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage()).isEqualTo("Bad value of " + ViolationConverters.THREADS_PROPERTY + ": -2");
+    }
+  }
+
+  @Test
+  public void propagate_converter_failure() throws Exception {
+    Callable<Object> callable = mock(Callable.class);
+    when(callable.call()).thenThrow(new IllegalStateException("Need to cry"));
+
+    List<Callable<Object>> callables = Lists.newArrayList(callable);
+    try {
+      new ViolationConverters(new Settings()).doExecute(new FakeTimerTask(), callables);
+      fail();
+    } catch (ExecutionException e) {
+      assertThat(e.getCause().getMessage()).isEqualTo("Need to cry");
+    }
+
+  }
+
+  static class FakeTimerTask extends TimerTask {
+    @Override
+    public void run() {
+    }
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ViolationMigrationTest.java b/sonar-server/src/test/java/org/sonar/server/db/migrations/violation/ViolationMigrationTest.java
new file mode 100644 (file)
index 0000000..360e699
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+package org.sonar.server.db.migrations.violation;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.core.persistence.TestDatabase;
+
+import java.util.Set;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class ViolationMigrationTest {
+
+  @Rule
+  public TestDatabase db = new TestDatabase().schema(getClass(), "schema.sql");
+
+  @Test
+  public void migrate_violations() throws Exception {
+    db.prepareDbUnit(getClass(), "migrate_violations.xml");
+
+    new ViolationMigration(db.database(), new Settings()).execute();
+
+    db.assertDbUnit(getClass(), "migrate_violations_result.xml", "issues", "issue_changes");
+    assertMigrationEnded();
+  }
+
+  @Test
+  public void no_violations_to_migrate() throws Exception {
+    db.prepareDbUnit(getClass(), "no_violations_to_migrate.xml");
+
+    new ViolationMigration(db.database(), new Settings()).execute();
+
+    db.assertDbUnit(getClass(), "no_violations_to_migrate_result.xml", "issues", "issue_changes");
+    assertMigrationEnded();
+  }
+
+  private void assertMigrationEnded() {
+    assertThat(db.count("select count(id) from rule_failures")).isEqualTo(0);
+
+    // Progress thread is dead
+    Set<Thread> threads = Thread.getAllStackTraces().keySet();
+    for (Thread thread : threads) {
+      assertThat(thread.getName()).isNotEqualTo(Progress.THREAD_NAME);
+    }
+  }
+}
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations.xml
deleted file mode 100644 (file)
index 5a97e0d..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<dataset>
-  <snapshots id="10" root_project_id="10" project_id="11"/>
-  <users id="200" login="fabrice"/>
-  <users id="201" login="julien"/>
-  <action_plans id="999" kee="PLAN-999"/>
-
-  <!-- violation without review -->
-  <rule_failures id="1" snapshot_id="10" rule_id="20" failure_level="2" message="the message" line="1234" cost="3.14"
-                 created_at="2012-01-05" checksum="ABCDE" permanent_id="1"/>
-
-  <!-- violation with review -->
-  <rule_failures id="2" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]"
-                 created_at="2012-01-05" checksum="FGHIJ" permanent_id="2"/>
-  <reviews id="1" rule_failure_permanent_id="2" MANUAL_VIOLATION="[false]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18"
-           STATUS="OPEN" RESOLUTION="[null]" USER_ID="[null]" ASSIGNEE_ID="201"/>
-
-  <review_comments ID="1" REVIEW_ID="1" USER_ID="200" REVIEW_TEXT="a comment" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
-
-  <!-- comment by unknown user -->
-  <review_comments ID="2" REVIEW_ID="1" USER_ID="999" REVIEW_TEXT="to be ignored because unknown user" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
-
-  <!-- manual violation -->
-  <rule_failures id="3" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]"
-                 created_at="2012-01-05" checksum="FGHIJ" permanent_id="3"/>
-  <reviews id="2" rule_failure_permanent_id="3" MANUAL_VIOLATION="[true]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18"
-           STATUS="RESOLVED" RESOLUTION="FIXED" USER_ID="200" ASSIGNEE_ID="201"/>
-
-  <action_plans_reviews review_id="1" action_plan_id="999"/>
-
-
-
-
-
-  <!-- to be truncated -->
-  <issues ID="1" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]"
-          ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE"
-          CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
-          ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]"
-          RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/>
-  <issue_changes ID="1" ISSUE_KEY="ABCDE"/>
-</dataset>
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations_result.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/convert_violations_result.xml
deleted file mode 100644 (file)
index 8c0330b..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<dataset>
-  <issues ID="2" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]"
-          ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE"
-          CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
-          ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]"
-          RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/>
-
-  <issues ID="3" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="22" SEVERITY="BLOCKER" KEE="[ignore]"
-          ACTION_PLAN_KEY="PLAN-999" ASSIGNEE="julien" AUTHOR_LOGIN="[null]" CHECKSUM="FGHIJ"
-          CREATED_AT="2012-01-05" EFFORT_TO_FIX="[null]" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
-          ISSUE_UPDATE_DATE="2013-05-18" LINE="[null]" MANUAL_SEVERITY="[true]" MESSAGE="another message" REPORTER="[null]"
-          RESOLUTION="[null]" STATUS="CONFIRMED" UPDATED_AT="2013-05-18"/>
-
-  <issues ID="4" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="22" SEVERITY="BLOCKER" KEE="[ignore]"
-          ACTION_PLAN_KEY="[null]" ASSIGNEE="julien" AUTHOR_LOGIN="[null]" CHECKSUM="FGHIJ"
-          CREATED_AT="2012-01-05" EFFORT_TO_FIX="[null]" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
-          ISSUE_UPDATE_DATE="2013-05-18" LINE="[null]" MANUAL_SEVERITY="[true]" MESSAGE="another message" REPORTER="fabrice"
-          RESOLUTION="FIXED" STATUS="RESOLVED" UPDATED_AT="2013-05-18"/>
-
-  <issue_changes id="2" KEE="[ignore]" ISSUE_KEY="[ignore]" CHANGE_TYPE="comment" CHANGE_DATA="a comment" USER_LOGIN="fabrice" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
-</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/schema.sql b/sonar-server/src/test/resources/org/sonar/server/db/migrations/ConvertViolationsToIssuesTest/schema.sql
deleted file mode 100644 (file)
index eb8d051..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
--- 3.5
-
-CREATE TABLE "SNAPSHOTS" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "CREATED_AT" TIMESTAMP,
-  "BUILD_DATE" TIMESTAMP,
-  "PROJECT_ID" INTEGER 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_PROJECT_ID" INTEGER,
-  "PERIOD1_MODE" VARCHAR(100),
-  "PERIOD1_PARAM" VARCHAR(100),
-  "PERIOD1_DATE" TIMESTAMP,
-  "PERIOD2_MODE" VARCHAR(100),
-  "PERIOD2_PARAM" VARCHAR(100),
-  "PERIOD2_DATE" TIMESTAMP,
-  "PERIOD3_MODE" VARCHAR(100),
-  "PERIOD3_PARAM" VARCHAR(100),
-  "PERIOD3_DATE" TIMESTAMP,
-  "PERIOD4_MODE" VARCHAR(100),
-  "PERIOD4_PARAM" VARCHAR(100),
-  "PERIOD4_DATE" TIMESTAMP,
-  "PERIOD5_MODE" VARCHAR(100),
-  "PERIOD5_PARAM" VARCHAR(100),
-  "PERIOD5_DATE" TIMESTAMP
-);
-
-CREATE TABLE "USERS" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "LOGIN" VARCHAR(40),
-  "NAME" VARCHAR(200),
-  "EMAIL" VARCHAR(100),
-  "CRYPTED_PASSWORD" VARCHAR(40),
-  "SALT" VARCHAR(40),
-  "CREATED_AT" TIMESTAMP,
-  "UPDATED_AT" TIMESTAMP,
-  "REMEMBER_TOKEN" VARCHAR(500),
-  "REMEMBER_TOKEN_EXPIRES_AT" TIMESTAMP,
-  "ACTIVE" BOOLEAN DEFAULT TRUE
-);
-
-
-CREATE TABLE "RULE_FAILURES" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "SNAPSHOT_ID" INTEGER NOT NULL,
-  "RULE_ID" INTEGER NOT NULL,
-  "FAILURE_LEVEL" INTEGER NOT NULL,
-  "MESSAGE" VARCHAR(4000),
-  "LINE" INTEGER,
-  "COST" DOUBLE,
-  "CREATED_AT" TIMESTAMP,
-  "CHECKSUM" VARCHAR(1000),
-  "PERMANENT_ID" INTEGER,
-  "SWITCHED_OFF" BOOLEAN,
-  "PERSON_ID" INTEGER
-);
-
-CREATE TABLE "ACTION_PLANS" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "KEE" VARCHAR(100),
-  "NAME" VARCHAR(200),
-  "DESCRIPTION" VARCHAR(1000),
-  "DEADLINE" TIMESTAMP,
-  "USER_LOGIN" VARCHAR(40),
-  "PROJECT_ID" INTEGER,
-  "STATUS" VARCHAR(10),
-  "CREATED_AT" TIMESTAMP,
-  "UPDATED_AT" TIMESTAMP
-);
-
-CREATE TABLE "ACTION_PLANS_REVIEWS" (
-  "ACTION_PLAN_ID" INTEGER,
-  "REVIEW_ID" INTEGER
-);
-
-CREATE TABLE "REVIEWS" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "CREATED_AT" TIMESTAMP,
-  "UPDATED_AT" TIMESTAMP,
-  "USER_ID" INTEGER,
-  "ASSIGNEE_ID" INTEGER,
-  "TITLE" VARCHAR(500),
-  "STATUS" VARCHAR(10),
-  "SEVERITY" VARCHAR(10),
-  "RULE_FAILURE_PERMANENT_ID" INTEGER,
-  "PROJECT_ID" INTEGER,
-  "RESOURCE_ID" INTEGER,
-  "RESOURCE_LINE" INTEGER,
-  "RESOLUTION" VARCHAR(200),
-  "RULE_ID" INTEGER,
-  "MANUAL_VIOLATION" BOOLEAN NOT NULL,
-  "MANUAL_SEVERITY" BOOLEAN NOT NULL,
-  "DATA" VARCHAR(4000)
-);
-
-CREATE TABLE "REVIEW_COMMENTS" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "CREATED_AT" TIMESTAMP,
-  "UPDATED_AT" TIMESTAMP,
-  "REVIEW_ID" INTEGER,
-  "USER_ID" INTEGER,
-  "REVIEW_TEXT" VARCHAR(16777215)
-);
-
-
-
--- 3.6
-
-CREATE TABLE "ISSUES" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "KEE" VARCHAR(50) UNIQUE NOT NULL,
-  "COMPONENT_ID" INTEGER NOT NULL,
-  "ROOT_COMPONENT_ID" INTEGER,
-  "RULE_ID" INTEGER,
-  "SEVERITY" VARCHAR(10),
-  "MANUAL_SEVERITY" BOOLEAN NOT NULL,
-  "MESSAGE" VARCHAR(4000),
-  "LINE" INTEGER,
-  "EFFORT_TO_FIX" DOUBLE,
-  "STATUS" VARCHAR(20),
-  "RESOLUTION" VARCHAR(20),
-  "CHECKSUM" VARCHAR(1000),
-  "REPORTER" VARCHAR(40),
-  "ASSIGNEE" VARCHAR(40),
-  "AUTHOR_LOGIN" VARCHAR(100),
-  "ACTION_PLAN_KEY" VARCHAR(50) NULL,
-  "ISSUE_ATTRIBUTES" VARCHAR(4000),
-  "ISSUE_CREATION_DATE" TIMESTAMP,
-  "ISSUE_CLOSE_DATE" TIMESTAMP,
-  "ISSUE_UPDATE_DATE" TIMESTAMP,
-  "CREATED_AT" TIMESTAMP,
-  "UPDATED_AT" TIMESTAMP
-);
-
-CREATE TABLE "ISSUE_CHANGES" (
-  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "KEE" VARCHAR(50),
-  "ISSUE_KEY" VARCHAR(50) NOT NULL,
-  "USER_LOGIN" VARCHAR(40),
-  "CHANGE_TYPE" VARCHAR(40),
-  "CHANGE_DATA"  VARCHAR(16777215),
-  "CREATED_AT" TIMESTAMP,
-  "UPDATED_AT" TIMESTAMP,
-);
\ No newline at end of file
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/migrate_violations.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/migrate_violations.xml
new file mode 100644 (file)
index 0000000..0d4a6e8
--- /dev/null
@@ -0,0 +1,39 @@
+<dataset>
+  <snapshots id="10" root_project_id="10" project_id="11"/>
+  <users id="200" login="fabrice"/>
+  <users id="201" login="julien"/>
+  <action_plans id="999" kee="PLAN-999"/>
+
+  <!-- violation without review -->
+  <rule_failures id="1" snapshot_id="10" rule_id="20" failure_level="2" message="the message" line="1234" cost="3.14"
+                 created_at="2012-01-05" checksum="ABCDE" permanent_id="1"/>
+
+  <!-- violation with review -->
+  <rule_failures id="2" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]"
+                 created_at="2012-01-05" checksum="FGHIJ" permanent_id="2"/>
+  <reviews id="1" rule_failure_permanent_id="2" MANUAL_VIOLATION="[false]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18"
+           STATUS="OPEN" RESOLUTION="[null]" USER_ID="[null]" ASSIGNEE_ID="201"/>
+
+  <review_comments ID="1" REVIEW_ID="1" USER_ID="200" REVIEW_TEXT="a comment" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+
+  <!-- comment by unknown user -->
+  <review_comments ID="2" REVIEW_ID="1" USER_ID="999" REVIEW_TEXT="to be ignored because unknown user" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+
+  <!-- manual violation -->
+  <rule_failures id="3" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]"
+                 created_at="2012-01-05" checksum="FGHIJ" permanent_id="3"/>
+  <reviews id="2" rule_failure_permanent_id="3" MANUAL_VIOLATION="[true]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18"
+           STATUS="RESOLVED" RESOLUTION="FIXED" USER_ID="200" ASSIGNEE_ID="201"/>
+
+  <action_plans_reviews review_id="1" action_plan_id="999"/>
+
+
+
+  <!-- Already there from a past migration that crashed -->
+  <issues ID="1" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]"
+          ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE"
+          CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
+          ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]"
+          RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/>
+  <issue_changes id="1" KEE="ABCDE" ISSUE_KEY="[ignore]" CHANGE_TYPE="comment" CHANGE_DATA="a comment" USER_LOGIN="fabrice" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/migrate_violations_result.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/migrate_violations_result.xml
new file mode 100644 (file)
index 0000000..cafbaaf
--- /dev/null
@@ -0,0 +1,64 @@
+<dataset>
+
+  <snapshots id="10" root_project_id="10" project_id="11"/>
+  <users id="200" login="fabrice"/>
+  <users id="201" login="julien"/>
+  <action_plans id="999" kee="PLAN-999"/>
+
+  <!-- violation without review -->
+  <!--<rule_failures id="1" snapshot_id="10" rule_id="20" failure_level="2" message="the message" line="1234" cost="3.14"-->
+                 <!--created_at="2012-01-05" checksum="ABCDE" permanent_id="1"/>-->
+
+  <!-- violation with review -->
+  <!--<rule_failures id="2" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]"-->
+                 <!--created_at="2012-01-05" checksum="FGHIJ" permanent_id="2"/>-->
+  <reviews id="1" rule_failure_permanent_id="2" MANUAL_VIOLATION="[false]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18"
+           STATUS="OPEN" RESOLUTION="[null]" USER_ID="[null]" ASSIGNEE_ID="201"/>
+
+  <review_comments ID="1" REVIEW_ID="1" USER_ID="200" REVIEW_TEXT="a comment" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+
+  <!-- comment by unknown user -->
+  <review_comments ID="2" REVIEW_ID="1" USER_ID="999" REVIEW_TEXT="to be ignored because unknown user" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+
+  <!-- manual violation -->
+  <!--<rule_failures id="3" snapshot_id="10" rule_id="22" failure_level="2" message="another message" line="[null]" cost="[null]"-->
+                 <!--created_at="2012-01-05" checksum="FGHIJ" permanent_id="3"/>-->
+  <reviews id="2" rule_failure_permanent_id="3" MANUAL_VIOLATION="[true]" MANUAL_SEVERITY="[true]" SEVERITY="BLOCKER" UPDATED_AT="2013-05-18"
+           STATUS="RESOLVED" RESOLUTION="FIXED" USER_ID="200" ASSIGNEE_ID="201"/>
+
+  <action_plans_reviews review_id="1" action_plan_id="999"/>
+
+
+
+  <!-- Already there from a past migration that crashed -->
+  <issues ID="1" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]"
+          ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE"
+          CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
+          ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]"
+          RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/>
+  <issue_changes id="1" KEE="ABCDE" ISSUE_KEY="[ignore]" CHANGE_TYPE="comment" CHANGE_DATA="a comment" USER_LOGIN="fabrice" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+
+
+
+
+
+  <issues ID="2" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]"
+          ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE"
+          CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
+          ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]"
+          RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/>
+
+  <issues ID="3" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="22" SEVERITY="BLOCKER" KEE="[ignore]"
+          ACTION_PLAN_KEY="PLAN-999" ASSIGNEE="julien" AUTHOR_LOGIN="[null]" CHECKSUM="FGHIJ"
+          CREATED_AT="2012-01-05" EFFORT_TO_FIX="[null]" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
+          ISSUE_UPDATE_DATE="2013-05-18" LINE="[null]" MANUAL_SEVERITY="[true]" MESSAGE="another message" REPORTER="[null]"
+          RESOLUTION="[null]" STATUS="CONFIRMED" UPDATED_AT="2013-05-18"/>
+
+  <issues ID="4" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="22" SEVERITY="BLOCKER" KEE="[ignore]"
+          ACTION_PLAN_KEY="[null]" ASSIGNEE="julien" AUTHOR_LOGIN="[null]" CHECKSUM="FGHIJ"
+          CREATED_AT="2012-01-05" EFFORT_TO_FIX="[null]" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
+          ISSUE_UPDATE_DATE="2013-05-18" LINE="[null]" MANUAL_SEVERITY="[true]" MESSAGE="another message" REPORTER="fabrice"
+          RESOLUTION="FIXED" STATUS="RESOLVED" UPDATED_AT="2013-05-18"/>
+
+  <issue_changes id="2" KEE="[ignore]" ISSUE_KEY="[ignore]" CHANGE_TYPE="comment" CHANGE_DATA="a comment" USER_LOGIN="fabrice" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/no_violations_to_migrate.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/no_violations_to_migrate.xml
new file mode 100644 (file)
index 0000000..416efbc
--- /dev/null
@@ -0,0 +1,14 @@
+<dataset>
+  <snapshots id="10" root_project_id="10" project_id="11"/>
+  <users id="200" login="fabrice"/>
+  <users id="201" login="julien"/>
+  <action_plans id="999" kee="PLAN-999"/>
+
+  <!-- Already there from a past migration that crashed -->
+  <issues ID="1" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]"
+          ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE"
+          CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
+          ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]"
+          RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/>
+  <issue_changes id="1" KEE="ABCDE" ISSUE_KEY="[ignore]" CHANGE_TYPE="comment" CHANGE_DATA="a comment" USER_LOGIN="fabrice" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/no_violations_to_migrate_result.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/no_violations_to_migrate_result.xml
new file mode 100644 (file)
index 0000000..416efbc
--- /dev/null
@@ -0,0 +1,14 @@
+<dataset>
+  <snapshots id="10" root_project_id="10" project_id="11"/>
+  <users id="200" login="fabrice"/>
+  <users id="201" login="julien"/>
+  <action_plans id="999" kee="PLAN-999"/>
+
+  <!-- Already there from a past migration that crashed -->
+  <issues ID="1" COMPONENT_ID="11" ROOT_COMPONENT_ID="10" RULE_ID="20" SEVERITY="MINOR" KEE="[ignore]"
+          ACTION_PLAN_KEY="[null]" ASSIGNEE="[null]" AUTHOR_LOGIN="[null]" CHECKSUM="ABCDE"
+          CREATED_AT="2012-01-05" EFFORT_TO_FIX="3.14" ISSUE_ATTRIBUTES="[null]" ISSUE_CLOSE_DATE="[null]" ISSUE_CREATION_DATE="2012-01-05"
+          ISSUE_UPDATE_DATE="2012-01-05" LINE="1234" MANUAL_SEVERITY="[false]" MESSAGE="the message" REPORTER="[null]"
+          RESOLUTION="[null]" STATUS="OPEN" UPDATED_AT="2012-01-05"/>
+  <issue_changes id="1" KEE="ABCDE" ISSUE_KEY="[ignore]" CHANGE_TYPE="comment" CHANGE_DATA="a comment" USER_LOGIN="fabrice" CREATED_AT="2012-04-28" UPDATED_AT="2012-04-29"/>
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/schema.sql b/sonar-server/src/test/resources/org/sonar/server/db/migrations/violation/ViolationMigrationTest/schema.sql
new file mode 100644 (file)
index 0000000..eb8d051
--- /dev/null
@@ -0,0 +1,152 @@
+-- 3.5
+
+CREATE TABLE "SNAPSHOTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "CREATED_AT" TIMESTAMP,
+  "BUILD_DATE" TIMESTAMP,
+  "PROJECT_ID" INTEGER 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_PROJECT_ID" INTEGER,
+  "PERIOD1_MODE" VARCHAR(100),
+  "PERIOD1_PARAM" VARCHAR(100),
+  "PERIOD1_DATE" TIMESTAMP,
+  "PERIOD2_MODE" VARCHAR(100),
+  "PERIOD2_PARAM" VARCHAR(100),
+  "PERIOD2_DATE" TIMESTAMP,
+  "PERIOD3_MODE" VARCHAR(100),
+  "PERIOD3_PARAM" VARCHAR(100),
+  "PERIOD3_DATE" TIMESTAMP,
+  "PERIOD4_MODE" VARCHAR(100),
+  "PERIOD4_PARAM" VARCHAR(100),
+  "PERIOD4_DATE" TIMESTAMP,
+  "PERIOD5_MODE" VARCHAR(100),
+  "PERIOD5_PARAM" VARCHAR(100),
+  "PERIOD5_DATE" TIMESTAMP
+);
+
+CREATE TABLE "USERS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "LOGIN" VARCHAR(40),
+  "NAME" VARCHAR(200),
+  "EMAIL" VARCHAR(100),
+  "CRYPTED_PASSWORD" VARCHAR(40),
+  "SALT" VARCHAR(40),
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP,
+  "REMEMBER_TOKEN" VARCHAR(500),
+  "REMEMBER_TOKEN_EXPIRES_AT" TIMESTAMP,
+  "ACTIVE" BOOLEAN DEFAULT TRUE
+);
+
+
+CREATE TABLE "RULE_FAILURES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "SNAPSHOT_ID" INTEGER NOT NULL,
+  "RULE_ID" INTEGER NOT NULL,
+  "FAILURE_LEVEL" INTEGER NOT NULL,
+  "MESSAGE" VARCHAR(4000),
+  "LINE" INTEGER,
+  "COST" DOUBLE,
+  "CREATED_AT" TIMESTAMP,
+  "CHECKSUM" VARCHAR(1000),
+  "PERMANENT_ID" INTEGER,
+  "SWITCHED_OFF" BOOLEAN,
+  "PERSON_ID" INTEGER
+);
+
+CREATE TABLE "ACTION_PLANS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "KEE" VARCHAR(100),
+  "NAME" VARCHAR(200),
+  "DESCRIPTION" VARCHAR(1000),
+  "DEADLINE" TIMESTAMP,
+  "USER_LOGIN" VARCHAR(40),
+  "PROJECT_ID" INTEGER,
+  "STATUS" VARCHAR(10),
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "ACTION_PLANS_REVIEWS" (
+  "ACTION_PLAN_ID" INTEGER,
+  "REVIEW_ID" INTEGER
+);
+
+CREATE TABLE "REVIEWS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP,
+  "USER_ID" INTEGER,
+  "ASSIGNEE_ID" INTEGER,
+  "TITLE" VARCHAR(500),
+  "STATUS" VARCHAR(10),
+  "SEVERITY" VARCHAR(10),
+  "RULE_FAILURE_PERMANENT_ID" INTEGER,
+  "PROJECT_ID" INTEGER,
+  "RESOURCE_ID" INTEGER,
+  "RESOURCE_LINE" INTEGER,
+  "RESOLUTION" VARCHAR(200),
+  "RULE_ID" INTEGER,
+  "MANUAL_VIOLATION" BOOLEAN NOT NULL,
+  "MANUAL_SEVERITY" BOOLEAN NOT NULL,
+  "DATA" VARCHAR(4000)
+);
+
+CREATE TABLE "REVIEW_COMMENTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP,
+  "REVIEW_ID" INTEGER,
+  "USER_ID" INTEGER,
+  "REVIEW_TEXT" VARCHAR(16777215)
+);
+
+
+
+-- 3.6
+
+CREATE TABLE "ISSUES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "KEE" VARCHAR(50) UNIQUE NOT NULL,
+  "COMPONENT_ID" INTEGER NOT NULL,
+  "ROOT_COMPONENT_ID" INTEGER,
+  "RULE_ID" INTEGER,
+  "SEVERITY" VARCHAR(10),
+  "MANUAL_SEVERITY" BOOLEAN NOT NULL,
+  "MESSAGE" VARCHAR(4000),
+  "LINE" INTEGER,
+  "EFFORT_TO_FIX" DOUBLE,
+  "STATUS" VARCHAR(20),
+  "RESOLUTION" VARCHAR(20),
+  "CHECKSUM" VARCHAR(1000),
+  "REPORTER" VARCHAR(40),
+  "ASSIGNEE" VARCHAR(40),
+  "AUTHOR_LOGIN" VARCHAR(100),
+  "ACTION_PLAN_KEY" VARCHAR(50) NULL,
+  "ISSUE_ATTRIBUTES" VARCHAR(4000),
+  "ISSUE_CREATION_DATE" TIMESTAMP,
+  "ISSUE_CLOSE_DATE" TIMESTAMP,
+  "ISSUE_UPDATE_DATE" TIMESTAMP,
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP
+);
+
+CREATE TABLE "ISSUE_CHANGES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "KEE" VARCHAR(50),
+  "ISSUE_KEY" VARCHAR(50) NOT NULL,
+  "USER_LOGIN" VARCHAR(40),
+  "CHANGE_TYPE" VARCHAR(40),
+  "CHANGE_DATA"  VARCHAR(16777215),
+  "CREATED_AT" TIMESTAMP,
+  "UPDATED_AT" TIMESTAMP,
+);
\ No newline at end of file